[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/__tests__/OrderBy.test.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-disable jest/no-mocks-import */
/* eslint-env jest */
import React from 'react'
import { render, fireEvent } from '@vtex/test-tools/react'
import { useDevice } from 'vtex.device-detector'
import { setMobileState } from 'vtex.render-runtime'

import OrderBy from '../OrderBy'
import { useRuntime } from '../__mocks__/vtex.render-runtime'

const mockUseRuntime = useRuntime

const mockSetQuery = jest.fn()

beforeEach(() => {
  jest.clearAllMocks()

  mockUseRuntime.mockImplementation(() => ({
    setQuery: mockSetQuery,
  }))

  useDevice.mockImplementation(() => ({
    isMobile: false,
  }))
})

describe('<OrderBy />', () => {
  const renderComponent = customProps => mobile => {
    setMobileState(mobile)

    const props = {
      orderBy: 'OrderByTopSaleDESC',
      message: 'Sort By',
      ...customProps,
    }

    return render(<OrderBy {...props} />)
  }

  afterEach(() => {
    setMobileState(false)
  })

  it('should shown dropdown box on mobile mode', () => {
    const { container } = renderComponent()(true)

    let dropdownContainer = container.querySelector('.orderByDropdown > .dn')
    const button = container.querySelector('button')

    // Expect Dropdown container be rendered with display none
    expect(dropdownContainer).toBeDefined()
    expect(dropdownContainer).not.toBeNull()

    fireEvent.click(button)
    dropdownContainer = container.querySelector('.orderByDropdown > .dn')

    // Expect Dropdown container be rendered without display none
    expect(dropdownContainer).toBeNull()
  })

  it('should match snapshot in web mod', () => {
    const { asFragment } = renderComponent()(false)

    expect(asFragment()).toMatchSnapshot()
  })

  it('should match snapshot in mobile', () => {
    const { asFragment } = renderComponent()(true)

    expect(asFragment()).toMatchSnapshot()
  })
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useBreadcrumb.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'

export interface Breadcrumb {
  name: string
}

export function useBreadcrumb(): Breadcrumb[] | null | undefined {
  const { searchQuery } = useSearchPage()

  return (
    searchQuery?.data?.productSearch?.breadcrumb ||
    searchQuery?.data?.facets?.breadcrumb
  )
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useShippingActions.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useEffect, useState } from 'react'
import { useShippingOptionState } from 'vtex.shipping-option-components/ShippingOptionContext'

import useShouldDisableFacet from './useShouldDisableFacet'

const shippingActionTypes = {
  delivery: 'DELIVERY',
  'pickup-nearby': 'DELIVERY',
  pickup: 'DELIVERY',
  'pickup-in-point': 'PICKUP_POINT',
}

const eventIdentifiers = {
  DELIVERY: 'addressLabel',
  PICKUP_POINT: 'pickupPointLabel',
}

const placeHolders = {
  DELIVERY: 'store/search.filter.shipping.action-button.delivery',
  PICKUP_POINT: 'store/search.filter.shipping.action-button.pickup-in-point',
}

const addressDependentValues = [
  'delivery',
  'pickup-in-point',
  'pickup-nearby',
  'pickup',
]

const useShippingActions = facet => {
  const actionType = shippingActionTypes[facet.value]
  const eventIdentifier = actionType ? eventIdentifiers[actionType] : null

  const [actionLabel, setActionLabel] = useState(placeHolders[actionType])
  const [isAddressSet, setIsAddressSet] = useState(false)
  const [isPickupSet, setIsPickupSet] = useState(false)

  const isAddressDependent =
    addressDependentValues.findIndex(value => facet.value === value) > -1

  const { zipcode, selectedPickup, city, addressLabel } =
    useShippingOptionState()

  useEffect(() => {
    const pickupPointLabel = selectedPickup
      ? selectedPickup.pickupPoint.friendlyName
      : ''

    const label =
      eventIdentifier === 'pickupPointLabel' ? pickupPointLabel : addressLabel

    if (label) {
      setActionLabel(label)

      if (isAddressDependent) {
        setIsAddressSet(true)
      }

      if (eventIdentifier === 'pickupPointLabel') {
        setIsPickupSet(true)
      }
    } else {
      setActionLabel(placeHolders[actionType])

      if (isAddressDependent) {
        setIsAddressSet(false)
      }

      if (eventIdentifier === 'pickupPointLabel') {
        setIsPickupSet(false)
      }
    }
  }, [
    actionType,
    city,
    eventIdentifier,
    isAddressDependent,
    zipcode,
    selectedPickup,
    addressLabel,
  ])

  const shouldDisable = useShouldDisableFacet(facet, isAddressSet, isPickupSet)

  if (facet.value === 'pickup-nearby' || facet.value === 'pickup') {
    return {
      actionLabel: null,
      actionType: null,
      shouldDisable,
    }
  }

  return {
    actionType,
    actionLabel,
    shouldDisable,
  }
}

export default useShippingActions

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useMergeResults.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useEffect, useMemo, useState } from 'react'
import type { UseAdsReturn } from '@vtex/ads-react'

interface UseMergeResultsProps {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  props: any
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  sponsoredSearchResult: UseAdsReturn<any>
}

const useMergeResults = ({
  props,
  sponsoredSearchResult,
}: UseMergeResultsProps) => {
  const [holdOrganic, setHoldOrganic] = useState(false)

  const isFirstPage = Boolean(
    (props as unknown as { from?: number })?.from === 0 ||
      (props as unknown as { page?: number })?.page === 1
  )

  const isAdsLoading = sponsoredSearchResult?.isLoading ?? false

  useEffect(() => {
    if (!isFirstPage) {
      setHoldOrganic(false)

      return
    }

    if (!isAdsLoading) {
      setHoldOrganic(false)

      return
    }

    setHoldOrganic(true)

    const ADS_WAIT_MS = 2000
    const timeoutId = setTimeout(() => {
      setHoldOrganic(false)
    }, ADS_WAIT_MS)

    return () => {
      clearTimeout(timeoutId)
    }
  }, [isAdsLoading, isFirstPage])

  const mergedProps = useMemo(() => {
    const newProps = { ...props }

    // While ads are loading (and within wait window), hide organic products to avoid flicker
    if (isFirstPage && holdOrganic && newProps.searchQuery) {
      newProps.searchQuery = {
        ...newProps.searchQuery,
        loading: true,
        data: {
          ...newProps.searchQuery.data,
          products: [],
          productSearch: {
            ...newProps.searchQuery.data.productSearch,
            products: [],
          },
        },
        products: [],
      }
    } else if (
      isFirstPage &&
      sponsoredSearchResult?.ads?.length &&
      newProps.searchQuery
    ) {
      const sponsoredProducts = sponsoredSearchResult.ads.map(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (ad: any) => ({
          ...(ad.product ?? ad),
          advertisement: ad.advertisement,
          cacheId: `ad-${ad.product?.cacheId ?? ''}`,
        })
      )

      const mergedProducts = [
        ...sponsoredProducts,
        ...(newProps.searchQuery.products || []),
      ]

      newProps.searchQuery = {
        ...newProps.searchQuery,
        data: {
          ...newProps.searchQuery.data,
          products: mergedProducts,
          productSearch: {
            ...newProps.searchQuery.data.productSearch,
            products: mergedProducts,
          },
        },
        products: mergedProducts,
      }
    }

    return newProps
  }, [props, sponsoredSearchResult, holdOrganic, isFirstPage])

  return mergedProps
}

export default useMergeResults

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useFacetNavigation.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
// eslint-disable-next-line no-restricted-imports
import { zip } from 'ramda'
import { useCallback, useState } from 'react'
import { useRuntime } from 'vtex.render-runtime'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'

import {
  isSingleOptionFilter,
  isRadioFilter,
  isToggleFilter,
} from '../constants/filterTypes'
import { useFilterNavigator } from '../components/FilterNavigatorContext'
import { newFacetPathName } from '../utils/slug'
import { HEADER_SCROLL_OFFSET } from '../constants/SearchHelpers'
import {
  MAP_CATEGORY_CHAR,
  MAP_QUERY_KEY,
  MAP_VALUES_SEP,
  PATH_SEPARATOR,
  FULLTEXT_QUERY_KEY,
  SELLER_QUERY_KEY,
} from '../constants'
import useSearchState from './useSearchState'
import { getMainSearches } from '../utils/compatibilityLayer'

const scrollOptions = {
  baseElementId: 'search-result-anchor',
  top: -HEADER_SCROLL_OFFSET,
}

const removeElementAtIndex = (strArray, index) =>
  strArray.filter((_, i) => i !== index)

const upsert = (array, item) => {
  const foundItemIndex = array.findIndex(
    e => e.value === item.value && e.map === item.map
  )

  if (foundItemIndex === -1) {
    array.push(item)
  } else {
    array[foundItemIndex] = item
  }
}

export const compareFacetWithQueryValues = (
  querySegment,
  mapSegment,
  facet
) => {
  return (
    decodeURIComponent(querySegment).toLowerCase() ===
      decodeURIComponent(facet.value).toLowerCase() && mapSegment === facet.map
  )
}

const replaceQueryForNewQueryFormat = (
  queryString,
  mapString,
  selectedFacets
) => {
  const queryArray = queryString.split(PATH_SEPARATOR)
  const mapArray = mapString.split(MAP_VALUES_SEP)
  const newQueryFormatArray = zip(queryArray, mapArray).map(
    ([querySegment, mapSegment]) => {
      const facetForQuery = selectedFacets.find(facet => {
        return compareFacetWithQueryValues(querySegment, mapSegment, facet)
      })

      if (!facetForQuery) {
        return querySegment
      }

      return newFacetPathName(facetForQuery)
    }
  )

  return newQueryFormatArray.join(PATH_SEPARATOR)
}

const removeMapForNewURLFormat = (map, selectedFacets) => {
  const mapArray = map.split(MAP_VALUES_SEP)
  const mapsToFilter = selectedFacets.reduce((acc, facet) => {
    return facet.map === MAP_CATEGORY_CHAR ||
      (facet.newQuerySegment &&
        facet.newQuerySegment.toLowerCase() !== facet.value.toLowerCase())
      ? acc.concat(facet.map)
      : acc
  }, [])

  return mapArray
    .filter(mapItem => !mapsToFilter.includes(mapItem))
    .join(MAP_VALUES_SEP)
}

const getCleanUrlParams = currentMap => {
  const urlParams = new URLSearchParams(window.location.search)

  urlParams.set(MAP_QUERY_KEY, currentMap)
  if (!currentMap) {
    urlParams.delete(MAP_QUERY_KEY)
  }

  urlParams.delete('page')

  return urlParams
}

/**
 * Removes other facets from the same group (key) from selectedFacets
 */
const removeOtherFacetsFromSameGroup = (selectedFacets, facetKey) => {
  return selectedFacets.filter(selectedFacet => selectedFacet.key !== facetKey)
}

/**
 * Removes a specific facet from selectedFacets by value and map
 */
const removeSpecificFacet = (selectedFacets, facetValue, facetMap) => {
  return selectedFacets.filter(
    selectedFacet =>
      selectedFacet.value !== facetValue && selectedFacet.map !== facetMap
  )
}

/**
 * Finds indices in query/map that match facets from the same group
 */
const findIndicesOfSameGroupFacets = (query, map, facet) => {
  const indicesToRemove = []

  zip(query, map).forEach(([value, valueMap], index) => {
    const existingFacet = { value, map: valueMap, key: facet.key }

    if (
      compareFacetWithQueryValues(value, valueMap, existingFacet) &&
      valueMap === facet.map
    ) {
      indicesToRemove.push(index)
    }
  })

  return indicesToRemove
}

/**
 * Removes multiple indices from query and map arrays
 */
const removeMultipleIndices = (query, map, indices) => {
  let updatedQuery = query
  let updatedMap = map

  // Remove in descending order to avoid affecting subsequent indices
  indices.reverse().forEach(index => {
    updatedQuery = removeElementAtIndex(updatedQuery, index)
    updatedMap = removeElementAtIndex(updatedMap, index)
  })

  return { query: updatedQuery, map: updatedMap }
}

/**
 * Finds the index of a specific facet in query/map
 */
const findFacetIndex = (query, map, facet) => {
  return zip(query, map).findIndex(([value, valueMap]) =>
    compareFacetWithQueryValues(value, valueMap, facet)
  )
}

/**
 * Generic handler for facet selection (adding to URL)
 * Handles both toggle and radio filters with their specific exclusion logic
 */
const handleFacetSelection = (query, map, facet, selectedFacets) => {
  const isToggle = isToggleFilter(facet.key)
  const isRadio = isRadioFilter(facet.key)

  // Remove other facets from the same group for exclusive filters (toggle/radio)
  let updatedSelectedFacets = selectedFacets
  let updatedQuery = query
  let updatedMap = map

  if (isToggle || isRadio) {
    // Remove other facets from the same group
    updatedSelectedFacets = removeOtherFacetsFromSameGroup(
      selectedFacets,
      facet.key
    )

    // Remove indices from query/map
    if (isToggle) {
      // Toggle: remove multiple indices (all from same group)
      const indicesToRemove = findIndicesOfSameGroupFacets(query, map, facet)
      const result = removeMultipleIndices(query, map, indicesToRemove)

      updatedQuery = result.query
      updatedMap = result.map
    }
    // Radio doesn't remove from query/map during selection, only during deselection
  }

  // Add the new selected facet
  upsert(updatedSelectedFacets, facet)

  return {
    query: updatedQuery,
    map: updatedMap,
    selectedFacets: updatedSelectedFacets,
  }
}

/**
 * Generic handler for facet deselection (removing from URL)
 */
const handleFacetDeselection = (query, map, facet, selectedFacets) => {
  const isRadio = isRadioFilter(facet.key)

  // Remove facet from selectedFacets
  const updatedSelectedFacets = isRadio
    ? removeOtherFacetsFromSameGroup(selectedFacets, facet.key)
    : removeSpecificFacet(selectedFacets, facet.value, facet.map)

  // Find and remove from query/map
  const facetIndex = findFacetIndex(query, map, facet)

  return {
    query: removeElementAtIndex(query, facetIndex),
    map: removeElementAtIndex(map, facetIndex),
    selectedFacets: updatedSelectedFacets,
  }
}

// eslint-disable-next-line max-params
const buildQueryAndMap = (
  querySegments,
  mapSegments,
  facets,
  selectedFacets
) => {
  const queryAndMap = facets.reduce(
    // eslint-disable-next-line max-params
    // The spread on facet is important so we can assign facet.newQuerySegment
    ({ query, map }, { ...facet }) => {
      const facetValue = facet.value
      const isToggle = isToggleFilter(facet.key)

      facet.newQuerySegment = newFacetPathName(facet)

      // Handle deselection for toggle filters (selected=false means removing from URL)
      if (isToggle && !facet.selected) {
        return handleFacetDeselection(query, map, facet, selectedFacets)
      }

      // Handle deselection for radio/other filters (selected=true means removing from URL)
      if (!isToggle && facet.selected) {
        return handleFacetDeselection(query, map, facet, selectedFacets)
      }

      // Handle selection for toggle filters (selected=true means adding to URL)
      if (isToggle && facet.selected) {
        const result = handleFacetSelection(query, map, facet, selectedFacets)

        query = result.query
        map = result.map
        selectedFacets = result.selectedFacets
      } else if (!isToggle && !facet.selected) {
        // Handle selection for radio/other filters (selected=false or undefined means adding to URL)
        upsert(selectedFacets, facet)
      }

      // Handle category positioning when category needs insertion in the middle
      if (facet.map === MAP_CATEGORY_CHAR) {
        const lastCategoryIndex = map.lastIndexOf(MAP_CATEGORY_CHAR)

        if (lastCategoryIndex >= 0 && lastCategoryIndex !== map.length - 1) {
          // Corner case: if we are adding a category but there are other filters other than category applied.
          // Add the new category filter to the right of the other categories.
          return {
            query: [
              ...query.slice(0, lastCategoryIndex + 1),
              facetValue,
              ...query.slice(lastCategoryIndex + 1),
            ],
            map: [
              ...map.slice(0, lastCategoryIndex + 1),
              facet.map,
              ...map.slice(lastCategoryIndex + 1),
            ],
          }
        }
      }

      // Default: append to the end
      return {
        query: [...query, facetValue],
        map: [...map, facet.map],
      }
    },
    { query: querySegments, map: mapSegments }
  )

  const newQueryMap = {
    query: queryAndMap.query.join(PATH_SEPARATOR),
    map: queryAndMap.map.join(MAP_VALUES_SEP),
  }

  return newQueryMap
}

// eslint-disable-next-line max-params
export const buildNewQueryMap = (
  fullTextSellerAndCollection,
  facets,
  selectedFacets,
  ignoreGlobalShipping,
  onShouldIgnore
) => {
  // RadioGroup behavior - only apply radio logic when radio filters are actually involved
  let shouldIgnore = ignoreGlobalShipping
  const selectedShippingFacet = facets?.find(facet =>
    isSingleOptionFilter(facet.key)
  )

  // Handle single option filters (radio and toggle) with unified logic
  if (selectedShippingFacet) {
    if (!selectedShippingFacet.selected) {
      // Remove only single option filters of the same key/type when deselecting
      selectedFacets = selectedFacets.filter(
        facet => facet.key !== selectedShippingFacet.key
      )
      shouldIgnore = false
      onShouldIgnore(false)
    } else {
      // Only radio filters use the "ignore" URL pattern
      // Toggle filters are included normally in the URL
      shouldIgnore = isRadioFilter(selectedShippingFacet.key)
      onShouldIgnore(shouldIgnore)
    }
  } else {
    // No single option filters involved - reset shouldIgnore
    shouldIgnore = false
    onShouldIgnore(false)
  }

  const querySegments = selectedFacets.map(facet => facet.value)
  const mapSegments = selectedFacets.map(facet => facet.map)
  const shouldAddIgnoreSegment = shouldIgnore && selectedShippingFacet

  if (shouldAddIgnoreSegment) {
    querySegments.push('ignore')
    mapSegments.push(selectedShippingFacet.key)
  }

  const { ft: fullText, seller } = fullTextSellerAndCollection || {}
  const hasFullTextSearch = typeof fullText === 'string' && fullText.length > 0

  if (hasFullTextSearch) {
    querySegments.push(fullText)
    mapSegments.push(FULLTEXT_QUERY_KEY)
  }

  if (seller && mapSegments.indexOf(SELLER_QUERY_KEY) === -1) {
    querySegments.push(seller)
    mapSegments.push(SELLER_QUERY_KEY)
  }

  return buildQueryAndMap(querySegments, mapSegments, facets, selectedFacets)
}

const useFacetNavigation = (selectedFacets, scrollToTop = 'none') => {
  const { navigate, setQuery, query: runtimeQuery } = useRuntime()
  const { map, query } = useFilterNavigator()
  const { fuzzy, operator, searchState } = useSearchState()
  const { searchQuery } = useSearchPage()
  const [ignoreGlobalShipping, setIgnoreGlobalShipping] = useState(false)
  const fullTextQuery = map.split(',').includes('ft')

  const mainSearches = getMainSearches(query, map)

  const navigateToFacet = useCallback(
    // eslint-disable-next-line max-params
    (
      maybeFacets,
      preventRouteChange = false,
      isReset = false,
      priceRange = undefined
    ) => {
      const facets = Array.isArray(maybeFacets) ? maybeFacets : [maybeFacets]
      const { query: currentQuery, map: currentMap } = buildNewQueryMap(
        mainSearches,
        facets,
        selectedFacets,
        ignoreGlobalShipping,
        should => setIgnoreGlobalShipping(should)
      )

      if (isReset) {
        setIgnoreGlobalShipping(false)
      }

      if (scrollToTop !== 'none') {
        window.scroll({ top: 0, left: 0, behavior: scrollToTop })
      }

      if (preventRouteChange) {
        const state =
          typeof sessionStorage !== 'undefined'
            ? sessionStorage.getItem('searchState') ?? searchState
            : searchState ?? undefined

        const queries = {
          ...(currentMap && { map: `${currentMap}` }),
          query: `/${isReset ? runtimeQuery.initialQuery : currentQuery}`,
          page: undefined,
          fuzzy: fullTextQuery ? fuzzy || undefined : undefined,
          operator: fullTextQuery ? operator || undefined : undefined,
          searchState: state,
          initialMap: runtimeQuery.initialMap ?? map,
          initialQuery: runtimeQuery.initialQuery ?? query,
          ...(isReset ? { priceRange: undefined } : { priceRange }),
        }

        setQuery(queries)

        return
      }

      let newQuery = replaceQueryForNewQueryFormat(currentQuery, currentMap, [
        ...selectedFacets,
        ...facets,
      ])

      const urlParams = getCleanUrlParams(
        removeMapForNewURLFormat(currentMap, [...selectedFacets, ...facets])
      )

      if (
        searchQuery?.variables &&
        (!urlParams.get('initialQuery') || !urlParams.get('initialMap'))
      ) {
        const { map: mapVariable, query: queryVariable } = searchQuery.variables

        urlParams.set('initialQuery', queryVariable)
        urlParams.set('initialMap', mapVariable)
      }

      if (fuzzy && fullTextQuery) {
        urlParams.set('fuzzy', fuzzy)
      }

      if (operator && fullTextQuery) {
        urlParams.set('operator', operator)
      }

      if (searchState) {
        urlParams.set(
          'searchState',
          sessionStorage.getItem('searchState') ?? searchState
        )
      }

      if (priceRange) {
        urlParams.set('priceRange', priceRange)
      }

      if (!newQuery || newQuery === 'ignore') {
        const { initialQuery, initialMap } = runtimeQuery

        if (!initialQuery || !initialMap) {
          return
        }

        newQuery = initialQuery
        urlParams.set('map', initialMap)
      }

      if (isReset) {
        urlParams.delete('priceRange')
      }

      navigate({
        to: `${PATH_SEPARATOR}${newQuery}`,
        query: urlParams.toString(),
        scrollOptions,
        modifiersOptions: {
          LOWERCASE: false,
        },
      })
    },
    [
      fullTextQuery,
      fuzzy,
      mainSearches,
      map,
      navigate,
      operator,
      query,
      runtimeQuery,
      scrollToTop,
      searchQuery,
      searchState,
      selectedFacets,
      setQuery,
      ignoreGlobalShipping,
    ]
  )

  return navigateToFacet
}

export default useFacetNavigation

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useSearchState.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'
import { path } from 'ramda'

const useSearchState = () => {
  const { searchQuery } = useSearchPage()

  return {
    fuzzy: path(['data', 'productSearch', 'fuzzy'], searchQuery),
    operator: path(['data', 'productSearch', 'operator'], searchQuery),
    searchState: path(['data', 'productSearch', 'searchState'], searchQuery),
  }
}

export default useSearchState

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useDataPixel.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable no-restricted-imports */
import { useEffect, useRef } from 'react'
import { usePixel } from 'vtex.pixel-manager/PixelContext'
import { isEmpty } from 'ramda'

type Data = unknown[] | unknown

const useDataPixel = (data: Data, pageIdentifier = '', isLoading = false) => {
  const { push } = usePixel()
  const previousIdRef = useRef<string | null>(null)

  const previousId = previousIdRef.current

  useEffect(() => {
    if (!pageIdentifier || isLoading || previousId === pageIdentifier) {
      return
    }

    if (!data || isEmpty(data)) {
      return
    }

    if (Array.isArray(data)) {
      data.forEach(push)
    } else {
      push(data)
    }

    previousIdRef.current = pageIdentifier
  }, [data, isLoading, pageIdentifier, previousId, push])
}

export default useDataPixel

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useFetchMore.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useState, useRef, useCallback, useEffect, useReducer } from 'react'
// eslint-disable-next-line no-restricted-imports
import { max } from 'ramda'
import { useRuntime } from 'vtex.render-runtime'
import {
  useSearchPageStateDispatch,
  useSearchPageState,
} from 'vtex.search-page-context/SearchPageContext'

import useSearchState from './useSearchState'

export const FETCH_TYPE = {
  NEXT: 'next',
  PREVIOUS: 'previous',
}

function reducer(state, action) {
  const { maxItemsPerPage, to, from, rollbackState } = action.args

  switch (action.type) {
    case 'RESET':
      return {
        page: 1,
        nextPage: 2,
        previousPage: 0,
        from: 0,
        to: maxItemsPerPage - 1,
      }

    case 'NEXT_PAGE':
      return {
        ...state,
        page: state.nextPage,
        nextPage: state.nextPage + 1,
        to,
      }

    case 'PREVIOUS_PAGE':
      return {
        ...state,
        page: state.previousPage,
        previousPage: state.previousPage - 1,
        from,
      }

    case 'ROLLBACK':
      return rollbackState

    default:
  }
}

const handleFetchMore = async (
  from,
  to,
  direction,
  fetchMoreLocked,
  setLoading,
  fetchMore,
  products,
  updateQueryError,
  fuzzy,
  operator,
  searchState
  // eslint-disable-next-line max-params
) => {
  if (fetchMoreLocked.current || products.length === 0) {
    return
  }

  const isForward = direction === FETCH_TYPE.NEXT

  fetchMoreLocked.current = true

  setLoading(true)

  return fetchMore({
    variables: {
      from,
      to,
      fuzzy,
      operator,
      searchState,
    },
    updateQuery: (prevResult, { fetchMoreResult }) => {
      setLoading(false)
      fetchMoreLocked.current = false

      if (!products || !fetchMoreResult) {
        updateQueryError.current = true

        return
      }

      // backwards compatibility
      if (prevResult && prevResult.search) {
        return {
          search: {
            ...prevResult.search,
            products: isForward
              ? [
                  ...prevResult.search.products,
                  ...fetchMoreResult.search.products,
                ]
              : [
                  ...fetchMoreResult.search.products,
                  ...prevResult.search.products,
                ],
          },
        }
      }

      return {
        ...fetchMoreResult,
        productSearch: {
          ...fetchMoreResult.productSearch,
          products: isForward
            ? [...products, ...fetchMoreResult.productSearch.products]
            : [...fetchMoreResult.productSearch.products, ...products],
        },
      }
    },
  }).catch(error => {
    setLoading(false)
    fetchMoreLocked.current = false
    updateQueryError.current = true

    return { error }
  })
}

const useFetchingMore = () => {
  const [loading, localSetMore] = useState(false)
  const { isFetchingMore } = useSearchPageState()
  const dispatch = useSearchPageStateDispatch()
  const setFetchMore = useCallback(
    value => {
      dispatch({ type: 'SET_FETCHING_MORE', args: { isFetchingMore: value } })
      localSetMore(value)
    },
    [dispatch]
  )

  const stateValue = isFetchingMore == null ? loading : isFetchingMore

  return [stateValue, setFetchMore]
}

export const useFetchMore = props => {
  const {
    page,
    maxItemsPerPage,
    fetchMore,
    products,
    queryData: { query, map, orderBy, priceRange },
  } = props

  const { setQuery, query: runtimeQuery } = useRuntime()
  const { fuzzy, operator, searchState } = useSearchState()
  const currentPage = (runtimeQuery.page && Number(runtimeQuery.page)) || page

  const initialState = {
    page: currentPage,
    nextPage: currentPage + 1,
    previousPage: currentPage - 1,
    from: (page - 1) * maxItemsPerPage,
    to: currentPage * maxItemsPerPage - 1,
  }

  const [pageState, pageDispatch] = useReducer(reducer, initialState)
  const [loading, setLoading] = useFetchingMore()
  const isFirstRender = useRef(true)
  const fetchMoreLocked = useRef(false) // prevents the user from sending two requests at once
  /* this is a temporary solution to deal with unexpected
  errors when the search result uses infinite scroll.
  This should be removed once infinite scrolling is removed */
  const [infiniteScrollError, setInfiniteScrollError] = useState(false)
  const updateQueryError = useRef(false) // TODO: refactor this ref

  useEffect(() => {
    if (!isFirstRender.current) {
      pageDispatch({ type: 'RESET', args: { maxItemsPerPage } })
    }

    isFirstRender.current = false
  }, [maxItemsPerPage, query, map, orderBy, priceRange])

  const handleFetchMoreNext = async () => {
    const rollbackState = pageState
    const from = pageState.to + 1
    const to = from + maxItemsPerPage - 1

    setInfiniteScrollError(false)
    pageDispatch({ type: 'NEXT_PAGE', args: { to } })

    setQuery(
      {
        page: pageState.nextPage,
      },
      { replace: true }
    )

    const promiseResult = await handleFetchMore(
      from,
      to,
      FETCH_TYPE.NEXT,
      fetchMoreLocked,
      setLoading,
      fetchMore,
      products,
      updateQueryError,
      fuzzy,
      operator,
      sessionStorage.getItem('searchState') ?? searchState
    )

    // success
    if (!promiseResult || !updateQueryError.current) {
      return
    }

    // if error, rollback
    pageDispatch({ type: 'ROLLBACK', args: { rollbackState } })
    setQuery({ page: pageState.page }, { replace: true })
    setInfiniteScrollError(true)
    updateQueryError.current = false
  }

  const handleFetchMorePrevious = async () => {
    const rollbackState = pageState
    const to = pageState.from - 1
    const from = max(0, to - maxItemsPerPage + 1)

    setInfiniteScrollError(false)
    pageDispatch({ type: 'PREVIOUS_PAGE', args: { from } })

    setQuery(
      {
        page: pageState.previousPage,
      },
      { replace: true, merge: true }
    )

    const promiseResult = await handleFetchMore(
      from,
      to,
      FETCH_TYPE.PREVIOUS,
      fetchMoreLocked,
      setLoading,
      fetchMore,
      products,
      updateQueryError,
      fuzzy,
      operator,
      sessionStorage.getItem('searchState') ?? searchState
    )

    // success
    if (!promiseResult || !updateQueryError.current) {
      return
    }

    // if error, rollback
    pageDispatch({ type: 'ROLLBACK', args: { rollbackState } })
    setQuery({ page: pageState.page }, { replace: true, merge: true })
    setInfiniteScrollError(true)
    updateQueryError.current = false
  }

  return {
    handleFetchMoreNext,
    handleFetchMorePrevious,
    loading,
    from: pageState.from,
    to: pageState.to,
    nextPage: pageState.nextPage,
    previousPage: pageState.previousPage,
    infiniteScrollError,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useMeasure.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useState, useEffect } from 'react'
import ResizeObserver from 'resize-observer-polyfill'

export default function useMeasure(ref) {
  const [bounds, setBounds] = useState({ left: 0, top: 0, width: 0, height: 0 })

  useEffect(() => {
    let isCurrent = true
    const resizeObserver = new ResizeObserver(([entry]) => {
      if (isCurrent) {
        setBounds(entry.contentRect)
      }
    })

    if (ref.current !== null) {
      resizeObserver.observe(ref.current)
    }

    return () => {
      resizeObserver.disconnect()
      isCurrent = false
    }
  }, [ref])

  return bounds
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useShouldDisableFacet.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'

import { MAP_VALUES_SEP } from '../constants'

export default function useShouldDisableFacet(
  facet,
  isAddressSet,
  isPickupSet
) {
  const { map } = useSearchPage()

  if (
    (facet.value === 'delivery' ||
      facet.value === 'pickup-nearby' ||
      facet.value === 'pickup') &&
    !isAddressSet
  ) {
    return true
  }

  if (facet.value === 'pickup-in-point' && !isPickupSet) {
    return true
  }

  if (facet.quantity === 0) {
    return true
  }

  if (!facet.selected || !map) {
    return false
  }

  const mapArray = map.split(MAP_VALUES_SEP)

  if (mapArray.includes('ft')) {
    return false
  }

  if (mapArray.length === 1) {
    return true
  }

  return false
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useSession.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useCallback } from 'react'
import { useRuntime } from 'vtex.render-runtime'

const useSession = () => {
  const { rootPath } = useRuntime()

  const getSession = useCallback(async () => {
    const headers = new Headers()

    headers.append('Content-Type', 'application/json')

    const requestOptions: RequestInit = {
      method: 'GET',
      headers,
      redirect: 'follow',
    }

    const session = await fetch(
      `${rootPath || ''}/api/sessions?items=public.shippingOption`,
      requestOptions
    )

    const data = await session.json()

    if (!data?.namespaces?.public?.shippingOption?.value) {
      return null
    }

    return JSON.parse(data.namespaces.public.shippingOption.value)
  }, [rootPath])

  return { getSession }
}

export default useSession

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useRedirect.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useRuntime } from 'vtex.render-runtime'

const useRedirect = () => {
  const { navigate } = useRuntime()

  const setRedirect = redirect => {
    if (!redirect) {
      return
    }

    const urlRegex = /((http(s)?:)?\/\/|www\.)/

    if (!urlRegex.test(redirect)) {
      navigate({
        to: redirect,
      })

      return
    }

    const originRedirect = redirect.replace(urlRegex, '')
    const origin = window.location.origin.replace(/(http(s)?:)?\/\//, '')

    if (originRedirect.startsWith(origin)) {
      navigate({
        to: originRedirect.replace(origin, ''),
      })
    } else {
      window.location.replace(redirect.replace(/^www/, 'https://www'))
    }
  }

  return { setRedirect }
}

export default useRedirect

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useSearchTitle.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useContext, useMemo } from 'react'

// This is here so this JS file can be imported
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import QueryContext from '../components/QueryContext'
import type { Breadcrumb } from './useBreadcrumb'

interface UseSearchTitleOptions {
  matchFt?: boolean
  fallbackToLastName?: boolean
}

function getLastName(breadcrumbs: Breadcrumb[]) {
  if (breadcrumbs.length === 0) {
    return undefined
  }

  return breadcrumbs[breadcrumbs.length - 1]?.name
}

function breadcrumbName(
  index: number,
  breadcrumbs: Breadcrumb[]
): string | undefined {
  return breadcrumbs[index]?.name
}

function isBrandPage(mapArray: string[]) {
  return mapArray[0] === 'b'
}

function findLastCategory(mapArray: string[]) {
  // .reverse() mutates the array. We don't want to do that with the original array
  const index = [...mapArray].reverse().findIndex(
    // traditional mapping for category (c)
    // or compatibility with VTEX IS approach (category-)
    mapItem => mapItem === 'c' || mapItem.startsWith('category-')
  )

  if (index === -1) {
    return index
  }

  // reverses the index back to normal
  return mapArray.length - 1 - index
}

const getQueryNameIndex = (mapArray: string[], matchFt: boolean) => {
  if (isBrandPage(mapArray)) {
    return 0
  }

  if (matchFt) {
    const ftIndex = mapArray.findIndex(mapItem => mapItem === 'ft')

    if (ftIndex >= 0) {
      return ftIndex
    }
  }

  const clusterIndex = mapArray.findIndex(
    mapItem => mapItem === 'productClusterIds'
  )

  if (clusterIndex >= 0) {
    return clusterIndex
  }

  const lastCategoryIndex = findLastCategory(mapArray)

  return lastCategoryIndex
}

export function useSearchTitle(
  breadcrumbs: Breadcrumb[],
  options?: UseSearchTitleOptions
) {
  const { map } = useContext(QueryContext)

  const matchFt = Boolean(options?.matchFt)

  const queryNameIndex = useMemo(() => {
    if (!map) {
      return -1
    }

    const mapArray = map.split(',')

    return getQueryNameIndex(mapArray, matchFt)
  }, [map, matchFt])

  let title = ''

  if (queryNameIndex >= 0) {
    title = breadcrumbName(queryNameIndex, breadcrumbs) ?? ''
  } else if (options?.fallbackToLastName) {
    title = getLastName(breadcrumbs) ?? ''
  }

  const decodedTitle = useMemo(() => {
    try {
      return decodeURIComponent(title)
    } catch {
      return title
    }
  }, [title])

  return decodedTitle
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useSelectedFilters.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { zip } from 'ramda'

import { useFilterNavigator } from '../components/FilterNavigatorContext'
import { isSameMap } from '../utils/queryAndMapUtils'

/**
 * This hook is required because we make the facets query
 * with only the categories and fulltext parameters, so we
 * need to calculate manually if the other filters are selected
 */
const useSelectedFilters = facets => {
  const { query, map } = useFilterNavigator()

  if (query == null && map == null) {
    return []
  }

  const queryAndMap = zip(
    query.toLowerCase().split('/').map(decodeURIComponent),
    map.split(',')
  )

  return facets.map(facet => {
    const currentFacetSlug = decodeURIComponent(facet.value).toLowerCase()

    const isSelected =
      queryAndMap.find(
        ([slug, slugMap]) =>
          slug === currentFacetSlug && isSameMap(slugMap, facet.map)
      ) !== undefined

    return {
      ...facet,
      selected: isSelected,
    }
  })
}

export default useSelectedFilters

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useRenderOnView.tsx from the search-result VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState, useRef } from 'react'

import { useOnView } from './useOnView'

const useRenderOnView = ({
  lazyRender = false,
  height = 400,
  offset = 200,
  waitForUserInteraction = true,
}) => {
  const dummy = useRef<HTMLDivElement | null>(null)
  const [hasBeenViewed, setHasBeenViewed] = useState(false)

  useOnView({
    ref: dummy,
    onView: () => setHasBeenViewed(true),
    once: true,
    initializeOnInteraction: waitForUserInteraction,
    bailOut: !lazyRender,
  })

  const dummyElement = (
    <div
      style={{
        width: '100%',
        height,
        position: 'relative',
      }}
    >
      <div
        ref={dummy}
        style={{
          /** Allows detecting the view a bit earlier, allowing rendering before the user
           * sees the whitespace where the component should be */
          top: -offset,
          bottom: 0,
          left: 0,
          right: 0,
          position: 'absolute',
        }}
      />
    </div>
  )

  return { hasBeenViewed: hasBeenViewed || !lazyRender, dummyElement }
}

export { useRenderOnView }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useOnView.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
/** Code borrowed from vtex.render-runtime. Maybe it should export this in the future */
import type { MutableRefObject } from 'react'
import { useEffect, useRef } from 'react'

interface IntersectionEvent {
  entry: IntersectionObserverEntry
  unobserve: () => void
}

interface HookOptions {
  ref: MutableRefObject<HTMLElement | null>
  onView?: (event: IntersectionEvent) => any
  threshold?: number
  once?: boolean
  bailOut?: boolean
  initializeOnInteraction?: boolean
}

/** Hook for detecting when an element is inside the viewport.
 * Differs from react-intersection-observer (https://www.npmjs.com/package/react-intersection-observer)
 * in that this hook doesn't use setState, using a callback approach instead, to improve
 * performance by preventing re-rendering.
 */
const useOnView = ({
  ref,
  onView,
  threshold = 0,
  once = false,
  bailOut = false,
  initializeOnInteraction = false,
}: HookOptions) => {
  const isObserving = useRef(false)
  const didIntersect = useRef(false)

  useEffect(() => {
    const initializeObserver = () => {
      const element = ref.current

      if (
        bailOut ||
        isObserving.current ||
        !element ||
        !onView ||
        (once && didIntersect.current)
      ) {
        return () => {}
      }

      isObserving.current = true

      const unobserve = () => {
        if (isObserving.current) {
          // eslint-disable-next-line @typescript-eslint/no-use-before-define
          observer.unobserve(element)
          isObserving.current = false
        }
      }

      const observer = new IntersectionObserver(([entry]) => {
        if (!entry.isIntersecting) {
          return
        }

        if (entry.intersectionRatio < threshold) {
          return
        }

        if (once) {
          unobserve()
        }

        didIntersect.current = true
        onView({ entry, unobserve })
      })

      observer.observe(element)

      return unobserve
    }

    if (initializeOnInteraction) {
      const cleanUpEvents = () => {
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        window?.document?.removeEventListener('scroll', handleInteraction)
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        window?.document?.removeEventListener('mouseover', handleInteraction)
      }

      let unobserve = () => {
        cleanUpEvents()
      }

      const handleInteraction = () => {
        cleanUpEvents()
        unobserve = initializeObserver()
      }

      // Checks if the user has scrolled the page before JS is loaded
      const scrollPosition = window.pageYOffset

      if (scrollPosition > 0) {
        handleInteraction()
      }

      window?.document?.addEventListener('scroll', handleInteraction)
      window?.document?.addEventListener('mouseover', handleInteraction)

      return unobserve
    }

    return initializeObserver()
  }, [bailOut, initializeOnInteraction, onView, once, ref, threshold])
}

export { useOnView }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/usePageView.ts from the search-result VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable no-restricted-globals */
import { useMemo } from 'react'
import { useRuntime, canUseDOM } from 'vtex.render-runtime'

import useDataPixel from './useDataPixel'

interface UsePageViewArgs {
  title?: string
  cacheKey?: string
  skip?: boolean
}

export const usePageView = ({
  title,
  cacheKey,
  skip,
}: UsePageViewArgs = {}) => {
  const { route, account } = useRuntime()
  const pixelCacheKey = cacheKey ?? route.routeId

  const eventData = useMemo(() => {
    if (!canUseDOM || skip) {
      return null
    }

    return {
      event: 'pageView',
      pageTitle: title ?? document.title,
      pageUrl: location.href,
      referrer:
        document.referrer.indexOf(location.origin) === 0
          ? undefined
          : document.referrer,
      accountName: account,
      routeId: route?.routeId ? route.routeId : '',
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [account, title, canUseDOM, pixelCacheKey])

  useDataPixel(skip ? null : eventData, pixelCacheKey)
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/hooks/useOutsideClick.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import { useLayoutEffect, useCallback } from 'react'

const useOutsideClick = (ref, handler, activeWhen) => {
  const handle = useCallback(
    e => ref && ref.current && !ref.current.contains(e.target) && handler(e),
    [handler, ref]
  )

  useLayoutEffect(() => {
    activeWhen && document.addEventListener('mousedown', handle)

    return () => document.removeEventListener('mousedown', handle)
  }, [activeWhen, handle])
}

export default useOutsideClick

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/PriceRange/PriceRangeInput.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useEffect, useState } from 'react'
import { InputCurrency, Button } from 'vtex.styleguide'
import { useRuntime } from 'vtex.render-runtime'
import { useCssHandles } from 'vtex.css-handles'
import { FormattedMessage } from 'react-intl'

const CSS_HANDLES = [
  'priceRangeInputWrapper',
  'priceRangeInput',
  'priceRangeInputButton',
]

const PriceRangeInput = ({ defaultValues, onSubmit, min, max }) => {
  const handles = useCssHandles(CSS_HANDLES)
  const { culture } = useRuntime()

  const [defaultLeft, defaultRight] = defaultValues

  const [values, setValues] = useState({
    left: defaultLeft,
    right: defaultRight,
  })

  useEffect(
    () => setValues({ left: defaultLeft, right: defaultRight }),
    [defaultLeft, defaultRight]
  )

  const handleChange = name => e => {
    const { value } = e.target

    if (name === 'min') {
      setValues(currentValues => ({
        ...currentValues,
        left: parseFloat(value),
      }))
    } else {
      setValues(currentValues => ({
        ...currentValues,
        right: parseFloat(value),
      }))
    }
  }

  const handleSubmit = () => {
    const { left: leftValue, right: rightValue } = values
    const left = Math.max(Math.min(leftValue, rightValue), min)
    const right = Math.min(Math.max(leftValue, rightValue), max)

    setValues({
      left,
      right,
    })

    const definedValues = [left, right]

    onSubmit(definedValues)
  }

  const handleKeyDown = e => {
    if (e.key === 'Enter') {
      handleSubmit()
    }
  }

  return (
    <div className={`${handles.priceRangeInputWrapper} flex justify-between`}>
      <div className={`${handles.priceRangeInput} mr2`}>
        <InputCurrency
          name="min"
          size="small"
          currencyCode={culture.currency}
          locale={culture.locale}
          value={values.left}
          onChange={handleChange('min')}
          onKeyDown={handleKeyDown}
        />
      </div>
      <div className={`${handles.priceRangeInput} mr2`}>
        <InputCurrency
          name="max"
          size="small"
          currencyCode={culture.currency}
          locale={culture.locale}
          value={values.right}
          onChange={handleChange('max')}
          onKeyDown={handleKeyDown}
        />
      </div>
      <Button
        className={handles.priceRangeInputButton}
        onClick={handleSubmit}
        size="small"
      >
        <FormattedMessage id="store/search-result.price-ranges.submit" />
      </Button>
    </div>
  )
}

export default PriceRangeInput

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/PriceRange/index.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useRef } from 'react'
import PropTypes from 'prop-types'
import { useRuntime } from 'vtex.render-runtime'
import { useIntl } from 'react-intl'
import { Slider } from 'vtex.styleguide'
import { formatCurrency } from 'vtex.format-currency'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'
import { usePixel } from 'vtex.pixel-manager'

import { pushFilterManipulationPixelEvent } from '../../utils/filterManipulationPixelEvents'
import { facetOptionShape } from '../../constants/propTypes'
import { getFilterTitle } from '../../constants/SearchHelpers'
import FilterOptionTemplate from '../FilterOptionTemplate'
import PriceRangeInput from './PriceRangeInput'

const DEBOUNCE_TIME = 500 // ms

/** Price range slider component */
const PriceRange = ({
  title,
  facets,
  priceRange,
  priceRangeLayout,
  scrollToTop,
  showClearByFilter,
  onChangePriceRange,
}) => {
  const { culture, setQuery, query: runtimeQuery } = useRuntime()
  const intl = useIntl()
  const navigateTimeoutId = useRef()

  const { push } = usePixel()
  const { searchQuery } = useSearchPage()

  const handleChange = ([left, right]) => {
    if (navigateTimeoutId.current) {
      clearTimeout(navigateTimeoutId.current)
    }

    pushFilterManipulationPixelEvent({
      name: 'PriceRange',
      value: `[${left.toString()}-${right.toString()}]`,
      products: searchQuery?.products ?? [],
      push,
    })

    navigateTimeoutId.current = setTimeout(() => {
      // avoid page from reloading again after clear all button is clicked
      if (runtimeQuery?.priceRange || left !== minValue || right !== maxValue) {
        onChangePriceRange(`${left} TO ${right}`)
      }

      if (scrollToTop !== 'none') {
        window.scroll({ top: 0, left: 0, behavior: scrollToTop })
      }
    }, DEBOUNCE_TIME)
  }

  const slugRegex = /^de-(.*)-a-(.*)$/
  const availableOptions = facets.filter(({ slug }) => slugRegex.test(slug))

  if (!availableOptions.length) {
    return null
  }

  let minValue = Number.MAX_VALUE
  let maxValue = Number.MIN_VALUE

  availableOptions.forEach(({ slug }) => {
    const [, minSlug, maxSlug] = slug.match(slugRegex)

    const min = parseInt(minSlug, 10)
    const max = parseInt(Math.ceil(maxSlug), 10)

    if (min < minValue) {
      minValue = min
    }

    if (max > maxValue) {
      maxValue = max
    }
  })

  const values = [minValue, maxValue]
  const currentValuesRegex = /^(.*) TO (.*)$/

  if (priceRange && currentValuesRegex.test(priceRange)) {
    const [, currentMin, currentMax] = priceRange.match(currentValuesRegex)

    values[0] = parseInt(currentMin, 10)
    values[1] = parseInt(currentMax, 10)
  }

  return (
    <FilterOptionTemplate
      id="priceRange"
      title={getFilterTitle(title, intl)}
      collapsable={false}
      handleClear={() => setQuery({ priceRange: undefined })}
      showClearByFilter={priceRange && showClearByFilter}
    >
      {priceRangeLayout === 'inputAndSlider' && (
        <PriceRangeInput
          defaultValues={values}
          onSubmit={newRange => handleChange(newRange)}
          max={maxValue}
          min={minValue}
        />
      )}
      <Slider
        min={minValue}
        max={maxValue}
        onChange={handleChange}
        formatValue={value => formatCurrency({ intl, culture, value })}
        values={values}
        defaultValues={values}
        range
      />
    </FilterOptionTemplate>
  )
}

PriceRange.propTypes = {
  /** Filter title */
  title: PropTypes.string.isRequired,
  /** Available price ranges */
  facets: PropTypes.arrayOf(facetOptionShape).isRequired,
  /** Current price range filter query parameter */
  priceRange: PropTypes.string,
  /** Price range layout (default or inputAndSlider) */
  priceRangeLayout: PropTypes.string,
  /** Scroll behavior */
  scrollToTop: PropTypes.string,
  /** Defines whether the price range should be cleared or not */
  clearPriceRange: PropTypes.bool,
  /** Set the value of clearPriceRange prop */
  setClearPriceRange: PropTypes.func,
  /** Whether a clear button that clear all options in a specific filter should appear beside the filter's name (true) or not (false). */
  showClearByFilter: PropTypes.bool,
  onChangePriceRange: PropTypes.func,
}

export default PriceRange

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/loaders/FetchMoreButton.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Fragment, useState, useEffect } from 'react'
import { Button } from 'vtex.styleguide'
import { useCssHandles } from 'vtex.css-handles'
import { FormattedMessage } from 'react-intl'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'
import { useRuntime } from 'vtex.render-runtime'

const CSS_HANDLES = [
  'buttonShowMore',
  'showingProducts',
  'showingProductsCount',
]

const useShowButton = (to, products, loading, recordsFiltered) => {
  const [showButton, setShowButton] = useState(
    !!products && Math.max(to + 1, products.length) < recordsFiltered
  )

  useEffect(() => {
    if (!loading) {
      setShowButton(
        !!products && Math.max(to + 1, products.length) < recordsFiltered
      )
    }
  }, [to, products, loading, recordsFiltered])

  return showButton
}

function shouldNotIncludeMap(map) {
  if (
    !map ||
    map === 'b' ||
    map === 'brand' ||
    map === 'c' ||
    map === 'category-1' ||
    map === 'department'
  ) {
    return true
  }

  const mapTree = map.split(',')

  if (mapTree.length > 3) {
    return false
  }

  return mapTree.every(mapItem => mapItem === 'c')
}

export function getMapQueryString(searchQuery, hideMap) {
  if (
    hideMap ||
    !searchQuery ||
    !searchQuery.variables ||
    shouldNotIncludeMap(searchQuery.variables.map)
  ) {
    return ''
  }

  return `&map=${searchQuery.variables.map}`
}

const FetchMoreButton = props => {
  const {
    products,
    to,
    recordsFiltered,
    onFetchMore,
    loading,
    showProductsCount,
    nextPage,
    htmlElementForButton,
  } = props

  const isAnchor = htmlElementForButton === 'a'
  const showButton = useShowButton(to, products, loading, recordsFiltered)
  const handles = useCssHandles(CSS_HANDLES)
  const { searchQuery } = useSearchPage()
  const { query } = useRuntime()
  const hideMap = !query?.map

  const handleFetchMoreClick = ev => {
    isAnchor && ev.preventDefault()
    onFetchMore()
  }

  return (
    <Fragment>
      <div className={`${handles.buttonShowMore} w-100 flex justify-center`}>
        {showButton && (
          <Button
            onClick={ev => handleFetchMoreClick(ev)}
            href={
              isAnchor &&
              `?page=${nextPage}${getMapQueryString(searchQuery, hideMap)}`
            }
            rel={isAnchor && 'next'}
            isLoading={loading}
            size="small"
            key={to} // Necessary to prevent focus after click
          >
            <FormattedMessage id="store/search-result.show-more-button" />
          </Button>
        )}
      </div>
      {showProductsCount && recordsFiltered && (
        <div
          className={`${handles.showingProducts} tc t-small pt3 c-muted-2 mt2`}
        >
          <FormattedMessage
            id="store/search-result.showing-products"
            tagName="span"
            values={{
              value: (
                <span className={`${handles.showingProductsCount} b`}>
                  <FormattedMessage
                    id="store/search-result.showing-products-count"
                    values={{
                      productsLoaded: products.length,
                      total: recordsFiltered,
                    }}
                  />
                </span>
              ),
            }}
          />
        </div>
      )}
    </Fragment>
  )
}

export default FetchMoreButton

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/loaders/LoadingSpinner.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import { Spinner } from 'vtex.styleguide'
import { useCssHandles } from 'vtex.css-handles'

const CSS_HANDLES = [
  'loadingSpinnerOuterContainer',
  'loadingSpinnerInnerContainer',
]

const LoadingSpinner = props => {
  const { loading } = props
  const handles = useCssHandles(CSS_HANDLES)

  return (
    loading && (
      <div
        className={`${handles.loadingSpinnerOuterContainer} w-100 flex justify-center`}
      >
        <div className={`${handles.loadingSpinnerInnerContainer} w3 ma0`}>
          <Spinner />
        </div>
      </div>
    )
  )
}

export default LoadingSpinner

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/loaders/FetchPreviousButton.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useState, useEffect } from 'react'
import { Button } from 'vtex.styleguide'
import { useCssHandles } from 'vtex.css-handles'
import { FormattedMessage } from 'react-intl'
import { useSearchPage } from 'vtex.search-page-context/SearchPageContext'
import { useRuntime } from 'vtex.render-runtime'

import { getMapQueryString } from './FetchMoreButton'

const CSS_HANDLES = ['buttonShowMore']

const useShowButton = (from, products, loading) => {
  const [showButton, setShowButton] = useState(
    !!products && from > 0 && products.length > 0
  )

  useEffect(() => {
    if (!loading) {
      setShowButton(!!products && from > 0 && products.length > 0)
    }
  }, [from, products, loading])

  return showButton
}

const FetchPreviousButton = props => {
  const {
    products,
    from,
    onFetchPrevious,
    loading,
    htmlElementForButton,
    previousPage,
  } = props

  const isAnchor = htmlElementForButton === 'a'
  const showButton = useShowButton(from, products, loading)
  const handles = useCssHandles(CSS_HANDLES)
  const { searchQuery } = useSearchPage()
  const { query } = useRuntime()
  const hideMap = !query?.map

  const handleFetchMoreClick = ev => {
    isAnchor && ev.preventDefault()
    onFetchPrevious()
  }

  return (
    <div className={`${handles.buttonShowMore} w-100 flex justify-center`}>
      {showButton && (
        <Button
          onClick={ev => handleFetchMoreClick(ev)}
          href={
            isAnchor &&
            `?page=${previousPage}${getMapQueryString(searchQuery, hideMap)}`
          }
          rel={isAnchor && 'prev'}
          isLoading={loading}
          size="small"
          key={from} // Necessary to prevent focus after click
        >
          <FormattedMessage id="store/search-result.show-previous-button" />
        </Button>
      )}
    </div>
  )
}

export default FetchPreviousButton

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/FacetItem.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useContext } from 'react'
import { useRuntime } from 'vtex.render-runtime'
import { Checkbox } from 'vtex.styleguide'

import QueryContext from '../../QueryContext'
import SettingsContext from '../../SettingsContext'
import { HEADER_SCROLL_OFFSET } from '../../../constants/SearchHelpers'

const scrollOptions = {
  baseElementId: 'search-result-anchor',
  top: -HEADER_SCROLL_OFFSET,
}

const removeElementAtIndex = (str, index, separator) =>
  str
    .split(separator)
    .filter((_, i) => i !== index)
    .join(separator)

const FacetItem = ({ facet, preventRouteChange = false }) => {
  const { navigate, setQuery } = useRuntime()
  const { query, map } = useContext(QueryContext)
  const { showFacetQuantity } = useContext(SettingsContext)

  const handleChange = () => {
    const urlParams = new URLSearchParams(window.location.search)

    if (facet.selected) {
      const facetIndex = query
        .toLowerCase()
        .split('/')
        .map(decodeURIComponent)
        .findIndex(
          value => value === decodeURIComponent(facet.value).toLowerCase()
        )

      if (preventRouteChange) {
        setQuery({
          map: removeElementAtIndex(map, facetIndex, ','),
          query: `/${removeElementAtIndex(query, facetIndex, '/')}`,
        })
      } else {
        urlParams.set('map', removeElementAtIndex(map, facetIndex, ','))

        navigate({
          to: `/${removeElementAtIndex(query, facetIndex, '/')}`,
          query: urlParams.toString(),
          scrollOptions,
        })
      }

      return
    }

    if (preventRouteChange) {
      setQuery({
        map: `${map},${facet.map}`,
        query: `/${query}/${facet.value}`,
      })
    } else {
      urlParams.set('map', `${map},${facet.map}`)

      navigate({
        to: `/${query}/${facet.value}`,
        query: urlParams.toString(),
        scrollOptions,
      })
    }
  }

  return (
    <div className="lh-copy w-100">
      <Checkbox
        id={facet.value}
        checked={facet.selected}
        label={
          showFacetQuantity ? `${facet.name} (${facet.quantity})` : facet.name
        }
        name={facet.name}
        onChange={handleChange}
        value={facet.name}
      />
    </div>
  )
}

export default FacetItem

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/searchResult.css from the search-result VTEX app.

[Assistant]
Here's the implementation:

```css
.container {
  display: grid;
  display: -ms-grid;
  grid-template-columns: 230px repeat(4, auto) 306px;
  -ms-grid-columns: 230px (1fr)[4] 306px;
  grid-template-rows: auto auto 60px auto;
  grid-template-areas:
    "breadcrumb breadcrumb breadcrumb breadcrumb breadcrumb breadcrumb"
    "title title title title title title"
    "filters quantity . . . orderby"
    "filters gallery gallery gallery gallery gallery"
    ". pagination pagination pagination pagination pagination";
}

.container a {
  outline: 0;
}

.buttonShowMore {
  -ms-grid-row: 3;
  -ms-grid-column: 2;
  -ms-grid-column-span: 5;
  grid-column: pagination;
  grid-row: pagination;
  justify-self: end;
  align-self: center;
}

.switch {
  -ms-grid-row: 1;
  -ms-grid-column: 6;
  grid-column: switch;
  grid-row: switch;
}

.breadcrumb {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  -ms-grid-column-span: 3;
  grid-column: breadcrumb;
  grid-row: breadcrumb;
  align-self: center;
  justify-self: stretch;
}

.filter span:hover {
  color: rgb(153, 175, 202)
}

.filters {
  -ms-grid-row: 2;
  -ms-grid-column: 1;
  grid-column: filters;
  grid-row: filters;
}

.resultGallery {
  -ms-grid-row: 2;
  -ms-grid-column: 2;
  -ms-grid-column-span: 5;
  grid-column-start: filters;
  grid-column-end: gallery;
  grid-row: gallery;
}

.filters ~ .resultGallery {
  grid-column-start: gallery;
}

.gallery {}

.galleryTitle {
  grid-column: title;
  grid-row: title;
}

.filterPopupButton {
  background: none;
  height: 3.3rem;
  border-top: none;
  border-left: none;
  border-right: none;
}

.accordionFilter {
  color: #828282;
}

.filterAccordionItemBox {
  height: 3.75rem;
}

.filterAccordionBreadcrumbs {
  height: 4.25rem;
}

.filterButtonsBox {
  height: 5rem;
}

.filterPopupFooter {
  margin-top: auto;
}

.accordionFilterItemOptions {
  padding-bottom: 9rem;
}

.dropdownMobile {
  margin-top: -2px;
}

.accordionFilterItemActive svg path {
  fill: #4f4f4f;
}

.totalProducts {
  -ms-grid-row: 1;
  -ms-grid-column: 5;
  grid-row: quantity;
  justify-self: start;
  align-self: center;
}

.filters ~ .totalProducts {
  grid-column: quantity;
  grid-row: quantity;
}

.orderBy {
  -ms-grid-row: 1;
  -ms-grid-column: 6;
  grid-column: orderby;
  grid-row: orderby;
  align-self: center;
  justify-self: stretch;
  display: flex;
}

.accordionFilterItemHidden {}

.accordionFilterItem {}

.filterAccordionItemBox {}

.accordionFilterItemTitle {}

.accordionFilterItemIcon {}

.filterSelected {}

.filterAvailable {}

.filterPopupTitle {}

.filterPopupArrowIcon {}

.footerButton {}

.layoutSwitcher {}

.buttonShowMore {}

.filterPopup {}

.filterPopupOpen {}

.filterPopupContent {}

.filterPopupContentContainer {}

.filterPopupContentContainerOpen {}

.selectedFilterCheckbox {}

.galleryItem {}

.searchNotFound {}

.filterTitle {
  font-size: 1rem;
}

.filterIcon {}

@media only screen and (max-width: 60em) {
  .container {
    -ms-grid-columns: 180px (1fr)[4] 306px;
    grid-template-columns: 180px repeat(4, auto) 285px;
    grid-template-rows: auto;
  }
}

@media only screen and (max-width: 50em) {
  .container {
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-areas:
      "filters orderby switch"
      "quantity quantity quantity"
      "gallery gallery gallery";
  }

  .totalProducts {
    border-top-style: solid;
    border-top-width: 2px;
    justify-self: stretch;
    grid-column: quantity;
  }
}

@media only screen and (min-width: 40em) {
  .sidebar {
    width: 16rem;
  }
}

@media only screen and (max-width: 40em) {
  .container {
    -ms-grid-columns: 2fr 1.5fr 0.2fr;
    grid-template-columns: 2fr 1.5fr 0.2fr;
    grid-template-areas:
      "orderby filters switch"
      "quantity quantity quantity"
      "gallery gallery gallery";
    grid-template-rows: auto;
  }

  .resultGallery {
    grid-column-start: gallery;
    grid-column-end: gallery;
  }

  .filterTitle {
    font-weight: 300;
  }
}

.filterPopupOpen {
  overflow: hidden;
}

.filterContent {}
```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/SideBar.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import { injectIntl } from 'react-intl'
import OutsideClickHandler from 'react-outside-click-handler'
import Animation from 'vtex.store-components/Animation'
import classNames from 'classnames'

import searchResult from './searchResult.css'

const OPEN_SIDEBAR_CLASS = 'overflow-hidden-ns'

/* SideBar component */
class Sidebar extends Component {
  updateComponent() {
    if (this.props.isOpen) {
      document.body.classList.add(OPEN_SIDEBAR_CLASS)
    } else {
      document.body.classList.remove(OPEN_SIDEBAR_CLASS)
    }
  }

  componentDidMount() {
    this.updateComponent()
  }

  componentDidUpdate() {
    this.updateComponent()
  }

  componentWillUnmount() {
    document.body.classList.remove(OPEN_SIDEBAR_CLASS)
  }

  render() {
    const { isOpen, onOutsideClick } = this.props

    if (typeof document === 'undefined') {
      return null
    }

    const scrimClasses = classNames(
      'fixed dim bg-base--inverted top-0 z-9999 w-100 vh-100 o-40 left-0',
      {
        dn: !isOpen,
      }
    )

    return ReactDOM.createPortal(
      <OutsideClickHandler onOutsideClick={onOutsideClick}>
        <div
          style={{ willChange: 'opacity' }}
          className={scrimClasses}
          onClick={onOutsideClick}
        />
        <Animation
          className={`${searchResult.sidebar} w-80 w-auto-ns h-100 fixed top-0 right-0 z-9999 bg-base shadow-2 flex flex-column`}
          isActive={isOpen}
          type="drawerLeft"
        >
          {this.props.children}
        </Animation>
      </OutsideClickHandler>,
      document.body
    )
  }
}

Sidebar.propTypes = {
  /* Internationalization */
  intl: PropTypes.any,
  /* Set the sideBar visibility */
  isOpen: PropTypes.bool,
  /* Sidebar content */
  children: PropTypes.node,
  /* Function to be called when click in the close sidebar button or outside the sidebar */
  onOutsideClick: PropTypes.func,
}

export default injectIntl(Sidebar)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/FilterSidebar.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import classNames from 'classnames'
import PropTypes from 'prop-types'
import { map, flatten, filter, prop, compose } from 'ramda'
import React, { useRef, useState, useEffect, Fragment, useContext } from 'react'
import { FormattedMessage } from 'react-intl'
import { useRuntime } from 'vtex.render-runtime'
import { Button } from 'vtex.styleguide'
import { IconFilter } from 'vtex.store-icons'

import AccordionFilterContainer from './AccordionFilterContainer'
import Sidebar from './SideBar'
import { facetOptionShape } from '../../../constants/propTypes'
import useSelectedFilters from './hooks/useSelectedFilters'
import searchResult from './searchResult.css'
import QueryContext from '../../QueryContext'
import isShippingOptionsComponent from '../../../utils/isShippingOptionsComponent'

const FilterSidebar = ({ filters, preventRouteChange = false }) => {
  const { navigate, setQuery } = useRuntime()
  const { query: queryField, map: mapField } = useContext(QueryContext)

  const [open, setOpen] = useState(false)
  const selectedFiltersFromProps = filter(
    prop('selected'),
    useSelectedFilters(compose(flatten, map(prop('facets')))(filters))
  )

  const [selectedFilters, setSelectedFilters] = useState(
    selectedFiltersFromProps
  )

  const lastSelectedFilters = useRef(selectedFilters)

  useEffect(() => {
    if (!open) {
      lastSelectedFilters.current = selectedFilters
    }
  }, [selectedFilters, open])

  const isOptionSelected = opt =>
    !!selectedFilters.find(facet => facet.value === opt.value)

  const handleFilterCheck = selectedFilter => {
    if (!isOptionSelected(selectedFilter)) {
      setSelectedFilters(selectedFilters.concat(selectedFilter))
    } else {
      setSelectedFilters(
        selectedFilters.filter(facet => facet.value !== selectedFilter.value)
      )
    }
  }

  const handleClose = e => {
    if (isShippingOptionsComponent(e)) {
      return
    }

    setOpen(false)
  }

  const handleOpen = () => {
    setOpen(true)
  }

  const handleClearFilters = () => {
    setSelectedFilters(lastSelectedFilters.current)
  }

  const handleApply = () => {
    const query = selectedFilters.map(facet => facet.value).join('/')
    const filtersMap = selectedFilters.map(facet => facet.map).join(',')

    if (preventRouteChange) {
      setQuery({
        map: `${mapField},${filtersMap}`,
        query: `/${queryField}/${query}`,
      })
    } else {
      const urlParams = new URLSearchParams(window.location.search)

      urlParams.set('map', filtersMap)

      navigate({
        to: `/${query}`,
        query: urlParams.toString(),
      })
    }

    setOpen(false)
  }

  return (
    <Fragment>
      <button
        className={classNames(
          `${searchResult.filterPopupButton} ph3 pv5 mv0 mv0 pointer flex justify-center items-center`,
          {
            'bb b--muted-1': open,
            bn: !open,
          }
        )}
        onClick={handleOpen}
      >
        <span
          className={`${searchResult.filterPopupTitle} c-on-base t-action--small ml-auto`}
        >
          <FormattedMessage id="store/search-result.filter-action.title" />
        </span>
        <span
          className={`${searchResult.filterPopupArrowIcon} ml-auto pl3 pt2`}
        >
          <IconFilter size={16} viewBox="0 0 17 17" />
        </span>
      </button>

      <Sidebar onOutsideClick={handleClose} isOpen={open}>
        <AccordionFilterContainer
          filters={filters}
          onFilterCheck={handleFilterCheck}
          selectedFilters={selectedFilters}
          isOptionSelected={isOptionSelected}
        />
        <div
          className={`${searchResult.filterButtonsBox} bt b--muted-5 bottom-0 fixed w-100 items-center flex z-1 bg-base`}
        >
          <div className="bottom-0 fl w-50 pl4 pr2">
            <Button
              block
              variation="tertiary"
              size="regular"
              onClick={handleClearFilters}
            >
              <FormattedMessage id="store/search-result.filter-button.clear" />
            </Button>
          </div>
          <div className="bottom-0 fr w-50 pr4 pl2">
            <Button
              block
              variation="secondary"
              size="regular"
              onClick={handleApply}
            >
              <FormattedMessage id="store/search-result.filter-button.apply" />
            </Button>
          </div>
        </div>
      </Sidebar>
    </Fragment>
  )
}

FilterSidebar.propTypes = {
  filters: PropTypes.arrayOf(
    PropTypes.shape({
      type: PropTypes.string,
      title: PropTypes.string.isRequired,
      facets: PropTypes.arrayOf(facetOptionShape),
    })
  ).isRequired,
  /** Prevents changing route when setting filters (uses URL search params instead) */
  preventRouteChange: PropTypes.bool,
}

export default FilterSidebar

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/index.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
/**
 * WARNING THIS COMPONENT IS DEPRECATED, PLEASE USE THE BLOCK "filter-navigator.v2". THIS COMPONENT WILL BE DELETED IN THE NEXT MAJOR.
 */

import classNames from 'classnames'
import PropTypes from 'prop-types'
import { map, flatten, filter, prop } from 'ramda'
import React, { useMemo } from 'react'
import ContentLoader from 'react-content-loader'
import { FormattedMessage } from 'react-intl'
import { useDevice } from 'vtex.device-detector'

import FilterSidebar from './FilterSidebar'
import SelectedFilters from './SelectedFilters'
import AvailableFilters from './AvailableFilters'
import {
  facetOptionShape,
  paramShape,
  hiddenFacetsSchema,
} from '../../../constants/propTypes'
import useSelectedFilters from './hooks/useSelectedFilters'
import getFilters from './utils/getFilters'

const getCategories = (tree = []) => {
  return [
    ...tree,
    ...flatten(tree.map(node => node.children && getCategories(node.children))),
  ].filter(Boolean)
}

/**
 * Wrapper around the filters (selected and available) as well
 * as the popup filters that appear on mobile devices
 */
const FilterNavigator = ({
  priceRange,
  tree = [],
  specificationFilters = [],
  priceRanges = [],
  brands = [],
  loading = false,
  hiddenFacets = {},
  preventRouteChange = false,
  initiallyCollapsed = false,
}) => {
  const { isMobile } = useDevice()

  const filters = getFilters({
    tree,
    specificationFilters,
    priceRanges,
    brands,
    hiddenFacets,
  })

  const selectedFilters = useSelectedFilters(
    useMemo(() => {
      const availableCategories = filter(prop('selected'), getCategories(tree))

      const options = [
        ...availableCategories,
        ...map(prop('facets'), specificationFilters),
        ...brands,
        ...priceRanges,
      ]

      return flatten(options)
    }, [brands, priceRanges, specificationFilters, tree])
  ).filter(facet => facet.selected)

  const filterClasses = classNames({
    'flex justify-center flex-auto bl br b--muted-5': isMobile,
  })

  if (loading && !isMobile) {
    return (
      <ContentLoader
        style={{
          width: '230px',
          height: '320px',
        }}
        width="230"
        height="320"
        y="0"
        x="0"
      >
        <rect width="100%" height="1em" />
        <rect width="100%" height="8em" y="1.5em" />
        <rect width="100%" height="1em" y="10.5em" />
        <rect width="100%" height="8em" y="12em" />
      </ContentLoader>
    )
  }

  if (isMobile) {
    return (
      <div className={filterClasses}>
        <FilterSidebar
          filters={filters}
          preventRouteChange={preventRouteChange}
        />
      </div>
    )
  }

  return (
    <div className={filterClasses}>
      <div className="bb b--muted-4">
        <h5 className="t-heading-5 mv5">
          <FormattedMessage id="store/search-result.filter-button.title" />
        </h5>
      </div>
      <SelectedFilters
        filters={selectedFilters}
        preventRouteChange={preventRouteChange}
      />
      <AvailableFilters
        filters={filters}
        priceRange={priceRange}
        preventRouteChange={preventRouteChange}
        initiallyCollapsed={initiallyCollapsed}
      />
    </div>
  )
}

FilterNavigator.propTypes = {
  /** Categories tree */
  tree: PropTypes.arrayOf(facetOptionShape),
  /** Params from pages */
  params: paramShape,
  /** List of brand filters (e.g. Samsung) */
  brands: PropTypes.arrayOf(facetOptionShape),
  /** List of specification filters (e.g. Android 7.0) */
  specificationFilters: PropTypes.arrayOf(
    PropTypes.shape({
      name: PropTypes.string.isRequired,
      facets: PropTypes.arrayOf(facetOptionShape),
    })
  ),
  /** List of price ranges filters (e.g. from-0-to-100) */
  priceRanges: PropTypes.arrayOf(facetOptionShape),
  /** Current price range filter query parameter */
  priceRange: PropTypes.string,
  /** Loading indicator */
  loading: PropTypes.bool,
  /** Prevents changing route when setting filters (uses URL search params instead) */
  preventRouteChange: PropTypes.bool,
  ...hiddenFacetsSchema,
}

export default FilterNavigator

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/SelectedFilters.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import PropTypes from 'prop-types'
import React from 'react'
import { useIntl } from 'react-intl'

import FacetItem from './FacetItem'
import FilterOptionTemplate from './FilterOptionTemplate'
import { facetOptionShape } from '../../../constants/propTypes'

/**
 * Search Filter Component.
 */
const SelectedFilters = ({ filters = [], preventRouteChange = false }) => {
  const intl = useIntl()
  const title = intl.formatMessage({ id: 'store/search.selected-filters' })

  return (
    <FilterOptionTemplate
      title={title}
      filters={filters}
      collapsable={false}
      selected
    >
      {facet => (
        <FacetItem
          key={facet.name}
          facet={facet}
          preventRouteChange={preventRouteChange}
        />
      )}
    </FilterOptionTemplate>
  )
}

SelectedFilters.propTypes = {
  /** Selected filters. */
  filters: PropTypes.arrayOf(facetOptionShape).isRequired,
  /** Prevents changing route when setting filters (uses URL search params instead) */
  preventRouteChange: PropTypes.bool,
}

export default SelectedFilters

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/AvailableFilters.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React from 'react'
import PropTypes from 'prop-types'

import SearchFilter from './SearchFilter'
import PriceRange from './PriceRange'

const AvailableFilters = ({
  filters = [],
  priceRange,
  preventRouteChange = false,
}) =>
  filters.map(filter => {
    const { type, title, facets, oneSelectedCollapse = false } = filter

    switch (type) {
      case 'PriceRanges':
        return (
          <PriceRange
            key={title}
            title={title}
            facets={facets}
            priceRange={priceRange}
          />
        )

      default:
        return (
          <SearchFilter
            key={title}
            title={title}
            facets={facets}
            preventRouteChange={preventRouteChange}
            oneSelectedCollapse={oneSelectedCollapse}
          />
        )
    }
  })

AvailableFilters.propTypes = {
  /** Filters to be displayed */
  filters: PropTypes.arrayOf(
    PropTypes.shape({
      title: PropTypes.string,
      type: PropTypes.string,
      oneSelectedCollapse: PropTypes.bool,
    })
  ),
  /** Prevents changing route when setting filters (uses URL search params instead) */
  preventRouteChange: PropTypes.bool,
  /** Price range query parameter */
  priceRange: PropTypes.string,
}

export default AvailableFilters

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/AccordionFilterItem.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { useIntl } from 'react-intl'
import classNames from 'classnames'
import { Checkbox } from 'vtex.styleguide'
import { IconCaret } from 'vtex.store-icons'

import { facetOptionShape } from '../../../constants/propTypes'
import { getFilterTitle } from '../../../constants/SearchHelpers'
import searchResult from './searchResult.css'

const AccordionFilterItem = ({
  title,
  facets,
  show,
  open,
  onOpen,
  onFilterCheck,
  isOptionSelected,
}) => {
  const intl = useIntl()
  const handleKeyDown = e => {
    if (e.key === ' ') {
      onOpen(e)
    }
  }

  return (
    <Fragment>
      {!open && (
        <div className="pl7">
          <div
            role="button"
            tabIndex={0}
            className={classNames(
              `${searchResult.accordionFilterItem} ${searchResult.filterAccordionItemBox} t-body pr5 pv3 pointer bb b--muted-5`,
              {
                [searchResult.accordionFilterItemActive]: open,
                [`${searchResult.accordionFilterItemHidden} dn`]: !show,
              }
            )}
            onKeyDown={handleKeyDown}
            onClick={onOpen}
          >
            <div
              className={classNames(
                `${searchResult.accordionFilterItemTitle} pv4`,
                {
                  'c-on-base t-small': open,
                  'c-on-base t-heading-5': !open,
                }
              )}
            >
              {getFilterTitle(title, intl)}
              <span className={`${searchResult.accordionFilterItemIcon} fr`}>
                <IconCaret orientation="down" size={10} />
              </span>
            </div>
          </div>
        </div>
      )}
      {open && (
        <div
          className={`${searchResult.accordionFilterItemOptions} pl7 overflow-scroll h-100`}
        >
          {facets.map(facet => {
            const { name } = facet

            return (
              <div
                className={`${searchResult.filterAccordionItemBox} pr4 pt3 items-center flex bb b--muted-5`}
                key={name}
              >
                <Checkbox
                  className="mb0"
                  checked={isOptionSelected(facet)}
                  id={name}
                  label={name}
                  name={name}
                  onChange={() => onFilterCheck(facet)}
                  value={name}
                />
              </div>
            )
          })}
        </div>
      )}
    </Fragment>
  )
}

AccordionFilterItem.propTypes = {
  /** Title */
  title: PropTypes.string,
  /** Available filter options */
  facets: PropTypes.arrayOf(facetOptionShape),
  /** Filter type (e.g. CATEGORIES_TYPE, BRANDS_TYPE) */
  type: PropTypes.string,
  /** Whether to show any of the content */
  show: PropTypes.bool,
  /** Whether to show the filter options */
  open: PropTypes.bool,
  /** Whether to hide other filters options when one is selected */
  oneSelectedCollapse: PropTypes.bool,
  /** Callback to open event */
  onOpen: PropTypes.func,
  /** Callback to filter option selected event */
  onItemSelected: PropTypes.func,
  /** Get the props to pass to render's Link */
  getLinkProps: PropTypes.func,
  /** Checkbox hit callback function */
  onFilterCheck: PropTypes.func,
  isOptionSelected: PropTypes.func,
}

export default AccordionFilterItem

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/QueryContext.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
// import { createContext } from 'react'

// const QueryContext = createContext({})

// export default QueryContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/AccordionFilterContainer.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { useIntl } from 'react-intl'
import classNames from 'classnames'
import { IconCaret } from 'vtex.store-icons'

import AccordionFilterItem from './AccordionFilterItem'
import searchResult from './searchResult.css'

const AccordionFilterContainer = ({
  filters,
  onFilterCheck,
  isOptionSelected,
}) => {
  const intl = useIntl()
  const [openItem, setOpenItem] = useState(null)

  const handleOpen = id => e => {
    e.preventDefault()

    if (openItem === id) {
      setOpenItem(null)
    } else {
      setOpenItem(id)
    }
  }

  const handleKeyDown = e => {
    if (e.key === ' ') {
      setOpenItem(null)
    }
  }

  const nonEmptyFilters = filters.filter(spec => spec.facets.length > 0)

  return (
    <div className={`${searchResult.accordionFilter} h-100`}>
      <div
        className={`${searchResult.filterAccordionBreadcrumbs} pointer flex flex-row items-center pa5 bg-base w-100 z-max bb b--muted-4`}
      >
        <div
          role="button"
          tabIndex={0}
          className="pv4 flex items-center"
          onClick={() => setOpenItem(null)}
          onKeyDown={handleKeyDown}
        >
          <div
            className={classNames('t-heading-4', {
              'c-muted-2': openItem,
              'c-on-base': !openItem,
            })}
          >
            {intl.formatMessage({
              id: 'store/search-result.filter-breadcrumbs.primary',
            })}
          </div>
        </div>
        {openItem && (
          <div className="pa4 flex items-center">
            <IconCaret orientation="right" size={13} />
            <div className="pl3 t-heading-4 c-on-base">
              {intl.formatMessage({ id: openItem })}
            </div>
          </div>
        )}
      </div>

      {nonEmptyFilters.map(filter => {
        const { title, facets } = filter
        const isOpen = openItem === filter.title

        return (
          <AccordionFilterItem
            key={filter.title}
            title={title}
            facets={facets}
            isOptionSelected={isOptionSelected}
            open={isOpen}
            onFilterCheck={onFilterCheck}
            show={!openItem || isOpen}
            onOpen={handleOpen(filter.title)}
          />
        )
      })}
    </div>
  )
}

AccordionFilterContainer.propTypes = {
  /** Current available filters */
  filters: PropTypes.arrayOf(PropTypes.object),
  /** Filters mapped for checkbox */
  filtersChecks: PropTypes.object,
  /** Checkbox hit callback function */
  onFilterCheck: PropTypes.func,
  /** Filters selected previously */
  selectedFilters: PropTypes.array,
  isOptionSelected: PropTypes.func.isRequired,
}

export default AccordionFilterContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/PriceRange.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import React, { useRef } from 'react'
import PropTypes from 'prop-types'
import { useRuntime } from 'vtex.render-runtime'
import { useIntl } from 'react-intl'
import { Slider } from 'vtex.styleguide'

import { facetOptionShape } from '../../../constants/propTypes'
import {
  getFilterTitle,
  HEADER_SCROLL_OFFSET,
} from '../../../constants/SearchHelpers'
import FilterOptionTemplate from './FilterOptionTemplate'

const DEBOUNCE_TIME = 500 // ms

/** Price range slider component */
const PriceRange = ({ title, facets, priceRange }) => {
  const {
    navigate,
    culture: { currency },
  } = useRuntime()

  const intl = useIntl()

  const navigateTimeoutId = useRef()

  const currencyOptions = {
    style: 'currency',
    currency,
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  const handleChange = ([left, right]) => {
    if (navigateTimeoutId.current) {
      clearTimeout(navigateTimeoutId.current)
    }

    navigateTimeoutId.current = setTimeout(() => {
      const queryParams = new URLSearchParams(window.location.search)

      queryParams.set('priceRange', `${left} TO ${right}`)

      navigate({
        to: window.location.pathname,
        query: queryParams.toString(),
        scrollOptions: {
          baseElementId: 'search-result-anchor',
          top: -HEADER_SCROLL_OFFSET,
        },
      })
    }, DEBOUNCE_TIME)
  }

  const slugRegex = /^de-(.*)-a-(.*)$/
  const availableOptions = facets.filter(({ slug }) => slugRegex.test(slug))

  if (!availableOptions.length) {
    return null
  }

  let minValue = Number.MAX_VALUE
  let maxValue = Number.MIN_VALUE

  availableOptions.forEach(({ slug }) => {
    const [, minSlug, maxSlug] = slug.match(slugRegex)

    const min = parseInt(minSlug, 10)
    const max = parseInt(maxSlug, 10)

    if (min < minValue) {
      minValue = min
    }

    if (max > maxValue) {
      maxValue = max
    }
  })

  const defaultValues = [minValue, maxValue]
  const currentValuesRegex = /^(.*) TO (.*)$/

  if (priceRange && currentValuesRegex.test(priceRange)) {
    const [, currentMin, currentMax] = priceRange.match(currentValuesRegex)

    defaultValues[0] = parseInt(currentMin, 10)
    defaultValues[1] = parseInt(currentMax, 10)
  }

  return (
    <FilterOptionTemplate
      title={getFilterTitle(title, intl)}
      collapsable={false}
    >
      <Slider
        min={minValue}
        max={maxValue}
        onChange={handleChange}
        defaultValues={defaultValues}
        formatValue={value => intl.formatNumber(value, currencyOptions)}
        range
      />
    </FilterOptionTemplate>
  )
}

PriceRange.propTypes = {
  /** Filter title */
  title: PropTypes.string.isRequired,
  /** Available price ranges */
  facets: PropTypes.arrayOf(facetOptionShape).isRequired,
  /** Current price range filter query parameter */
  priceRange: PropTypes.string,
}

export default PriceRange

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/SettingsContext.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
// import { createContext } from 'react'

// const SettingsContext = createContext({})

// export default SettingsContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of search-result/react/components/FilterNavigator/legacy/FilterOptionTemplate.js from the search-result VTEX app.

[Assistant]
Here's the implementation:

```javascript
import PropTypes from 'prop-types'
import React, { useState, useCallback } from 'react'
import { Collapse } from 'react-collapse'
import classNames from 'classnames'
import { IconCaret } from 'vtex.store-icons'

import searchResult from './searchResult.css'

/**
 * Collapsable filters container
 */
const FilterOptionTemplate = ({
  selected = false,
  title,
  collapsable = true,
  children,
  filters,
}) => {
  const [open, setOpen] = useState(true)

  const renderChildren = () => {
    if (typeof children !== 'function') {
      return children
    }

    return filters.map(children)
  }

  const handleKeyDown = useCallback(
    e => {
      if (e.key === ' ') {
        e.preventDefault()
        setOpen(!open)
      }
    },
    [open]
  )

  const containerClassName = classNames(searchResult.filter, 'pv5', {
    [searchResult.filterSelected]: selected,
    [searchResult.filterAvailable]: !selected,
  })

  const titleClassName = classNames(
    searchResult.filterTitle,
    't-heading-6 flex items-center justify-between',
    {
      ttu: selected,
    }
  )

  return (
    <div className="bb b--muted-4">
      <div className={containerClassName}>
        <div
          role="button"
          tabIndex={0}
          className={collapsable ? 'pointer' : ''}
          onClick={() => setOpen(!open)}
          onKeyDown={handleKeyDown}
        >
          <div className={titleClassName}>
            {title}
            {collapsable && (
              <span
                className={classNames(
                  searchResult.filterIcon,
                  'flex items-center ph5 c-muted-3'
                )}
              >
                <IconCaret orientation={open ? 'up' : 'down'} size={14} />
              </span>
            )}
          </div>
        </div>
      </div>
      <div
        className={classNames({
          'overflow-y-auto': collapsable,
          pb5: open,
        })}
        style={{ maxHeight: '200px' }}
        aria-hidden={!open}
      >
        {collapsable ? (
          <Collapse
            isOpened={open}
            theme={{ content: searchResult.filterContent }}
          >
            {renderChildren()}
          </Collapse>
        ) : (
          renderChildren()
        )}
      </div>
    </div>
  )
}

FilterOptionTemplate.propTypes = {
  /** Filters to be shown, if no filter is provided, treat the children as simple node */
  filters: PropTypes.arrayOf(PropTypes.object),
  /** Function to handle filter rendering or node if no filter is provided */
  children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
  /** Title */
  title: PropTypes.node,
  /** Whether collapsing is enabled */
  collapsable: PropTypes.bool,
  /** Whether it represents the selected filters */
  selected: PropTypes.bool,
}

export default FilterOptionTemplate

```