diff --git a/packages/api/src/platforms/vtex/resolvers/offer.ts b/packages/api/src/platforms/vtex/resolvers/offer.ts index 03e22084cc..d0f87da397 100644 --- a/packages/api/src/platforms/vtex/resolvers/offer.ts +++ b/packages/api/src/platforms/vtex/resolvers/offer.ts @@ -87,7 +87,7 @@ export const StoreOffer: Record> = { }, listPrice: (root) => { if (isSearchItem(root)) { - return root.ListPrice + return root.ListPrice ?? 0 } if (isOrderFormItem(root)) { diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 17e6e968e9..5e6ea47b8d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -79,7 +79,14 @@ export { parse as parseSearchState } from './search/serializer' export { default as formatSearchState } from './utils/format' -export { initialize as initSearchState } from './search/useSearchState' +export { + initialize as initSearchState, + isSearchSort, + removeFacet, + setFacet, + toggleFacet, + toggleFacets, +} from './search/useSearchState' export { Provider as SearchProvider } from './search/Provider' export { useSearch } from './search/useSearch' export { usePagination } from './search/usePagination' diff --git a/packages/sdk/src/search/serializer.ts b/packages/sdk/src/search/serializer.ts index be30e4d41b..818743e4a5 100644 --- a/packages/sdk/src/search/serializer.ts +++ b/packages/sdk/src/search/serializer.ts @@ -1,8 +1,8 @@ import type { SearchSort, State } from '../types' -import { initialize, reducer } from './useSearchState' +import { initialize, setFacet } from './useSearchState' export const parse = ({ pathname, searchParams }: URL): State => { - let state = initialize({ + const state = initialize({ base: pathname, term: searchParams.get('q') ?? null, sort: (searchParams.get('sort') as SearchSort) ?? undefined, @@ -15,9 +15,9 @@ export const parse = ({ pathname, searchParams }: URL): State => { const values = searchParams.getAll(facet) for (const value of values) { - state = reducer(state, { - type: 'setFacet' as const, - payload: { facet: { key: facet, value }, unique: false }, + state.selectedFacets = setFacet(state.selectedFacets, { + key: facet, + value, }) } } diff --git a/packages/sdk/src/search/useInfiniteSearchState.ts b/packages/sdk/src/search/useInfiniteSearchState.ts index ce213e3e67..e29089373a 100644 --- a/packages/sdk/src/search/useInfiniteSearchState.ts +++ b/packages/sdk/src/search/useInfiniteSearchState.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-case-declarations */ import { useMemo, useReducer } from 'react' import { SDKError } from '../utils/error' @@ -12,18 +11,30 @@ type Action = | { type: 'addNext' } + | { + type: 'reset' + payload: number + } const reducer = (state: State, action: Action) => { switch (action.type) { - case 'addPrev': + case 'addPrev': { const prev = state[0] - 1 return [prev, ...state] + } - case 'addNext': + case 'addNext': { const next = Number(state[state.length - 1]) + 1 return [...state, next] + } + + case 'reset': { + const { payload } = action + + return [payload] + } default: throw new SDKError('Unknown action for infinite search') @@ -31,14 +42,14 @@ const reducer = (state: State, action: Action) => { } export const useSearchInfiniteState = (initialPage: number) => { - const [pages, dispatch] = useReducer(reducer, initialPage, () => [ - initialPage, - ]) + const [pages, dispatch] = useReducer(reducer, undefined, () => [initialPage]) const actions = useMemo( () => ({ addPrevPage: () => dispatch({ type: 'addPrev' }), addNextPage: () => dispatch({ type: 'addNext' }), + resetInfiniteScroll: (page: number) => + dispatch({ type: 'reset', payload: page }), }), [] ) diff --git a/packages/sdk/src/search/useSearchState.ts b/packages/sdk/src/search/useSearchState.ts index 5fe20abcef..4536ce1a26 100644 --- a/packages/sdk/src/search/useSearchState.ts +++ b/packages/sdk/src/search/useSearchState.ts @@ -29,162 +29,53 @@ export const initialize = ({ page, }) -const isSearchSort = (x: string): x is SearchSort => sortKeys.has(x as any) - -type Action = - | { - type: 'setSort' - payload: SearchSort - } - | { - type: 'setTerm' - payload: string | null - } - | { - type: 'setPage' - payload: number - } - | { - type: 'setFacet' - payload: { facet: Facet; unique: boolean } - } - | { - type: 'setFacets' - payload: Facet[] - } - | { - type: 'removeFacet' - payload: Facet - } - | { - type: 'toggleFacet' - payload: Facet - } - | { - type: 'toggleFacets' - payload: Facet[] - } - const equals = (s1: State, s2: State) => format(s1).href === format(s2).href -const removeFacet = (state: State, facet: Facet): State => { +export const isSearchSort = (x: string): x is SearchSort => + sortKeys.has(x as any) + +export const removeFacet = (facets: Facet[], facet: Facet): Facet[] => { const { value } = facet - const index = state.selectedFacets.findIndex((x) => x.value === value) + const index = facets.findIndex((x) => x.value === value) if (index < 0) { throw new SDKError(`Cannot remove ${value} from search params`) } - // We can't allow removing the first facet, otherwise we would loose - // the navigation context - // - // TODO: Remove returning the base selected facets to the frontend since - // we won't be unselecting it anyways - return { - ...state, - selectedFacets: state.selectedFacets.filter( - (_, it) => it === 0 || it !== index - ), - } + return facets.filter((_, it) => it === 0 || it !== index) } -const setFacet = (state: State, facet: Facet, unique: boolean): State => { +export const setFacet = ( + facets: Facet[], + facet: Facet, + unique?: boolean +): Facet[] => { if (unique === true) { - const index = state.selectedFacets.findIndex((f) => f.key === facet.key) + const index = facets.findIndex((f) => f.key === facet.key) if (index > -1) { - return { - ...state, - selectedFacets: state.selectedFacets.map((f, it) => - it === index ? facet : f - ), - } + return facets.map((f, it) => (it === index ? facet : f)) } } - return { - ...state, - selectedFacets: [...state.selectedFacets, facet], - } + return [...facets, facet] } -const toggleFacet = (state: State, item: Facet) => { - const found = state.selectedFacets.find( +export const toggleFacet = (facets: Facet[], item: Facet) => { + const found = facets.find( (facet) => facet.key === item.key && facet.value === item.value ) if (found !== undefined) { - return removeFacet(state, item) + return removeFacet(facets, item) } - return setFacet(state, item, false) -} - -const toggleFacets = (state: State, items: Facet[]) => - items.reduce((item, s) => toggleFacet(item, s), state) - -export const reducer = (state: State, action: Action) => { - switch (action.type) { - case 'setSort': - if (!isSearchSort(action.payload)) { - throw new SDKError(`Sort param ${action.payload} is unknown`) - } - - return state.sort === action.payload - ? state - : { - ...state, - sort: action.payload, - } - - case 'setTerm': - return state.term === action.payload - ? state - : { - ...state, - term: action.payload, - } - - case 'setPage': - return state.page === action.payload - ? state - : { - ...state, - page: action.payload, - } - - case 'setFacet': - return setFacet(state, action.payload.facet, action.payload.unique) - - case 'setFacets': - return state.selectedFacets !== action.payload - ? { ...state, selectedFacets: action.payload } - : state - - case 'removeFacet': - return removeFacet(state, action.payload) - - case 'toggleFacet': - return toggleFacet(state, action.payload) - - case 'toggleFacets': - return toggleFacets(state, action.payload) - - default: - throw new SDKError(`Unknown action of search state machine`) - } + return setFacet(facets, item, false) } -const dispatcher = (onChange: (url: URL) => void, state: State) => ( - action: Action -) => { - const newState = reducer(state, action) - - if (!equals(newState, state)) { - onChange(format(newState)) - } -} +export const toggleFacets = (facets: Facet[], items: Facet[]) => + items.reduce((acc, curr) => toggleFacet(acc, curr), facets) export const useSearchState = ( initialState: Partial, @@ -192,31 +83,14 @@ export const useSearchState = ( ) => { const state = useMemo(() => initialize(initialState), [initialState]) - return useMemo(() => { - const dispatch = dispatcher(onChange, state) - - return { + return useMemo( + () => ({ state, - setSort: (sort: SearchSort) => - dispatch({ type: 'setSort', payload: sort }), - setTerm: (term: string | null) => - dispatch({ type: 'setTerm', payload: term }), - setPage: (page: number) => dispatch({ type: 'setPage', payload: page }), - setFacet: (facet: Facet, unique = false) => - dispatch({ type: 'setFacet', payload: { facet, unique } }), - setFacets: (facets: Facet[]) => - dispatch({ type: 'setFacets', payload: facets }), - removeFacet: (facet: Facet) => - dispatch({ type: 'removeFacet', payload: facet }), - toggleFacet: (facet: Facet) => - dispatch({ - type: 'toggleFacet', - payload: facet, - }), - toggleFacets: (facets: Facet[]) => - dispatch({ type: 'toggleFacets', payload: facets }), - } - }, [onChange, state]) + setState: (newState: State) => + !equals(newState, state) && onChange(format(newState)), + }), + [onChange, state] + ) } export type UseSearchState = ReturnType