@graphcommerce/magento-product 9.0.0-canary.57 → 9.0.0-canary.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 9.0.0-canary.59
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2309](https://github.com/graphcommerce-org/graphcommerce/pull/2309) [`1fe4090`](https://github.com/graphcommerce-org/graphcommerce/commit/1fe409029671b841e582406251e45a9555fc78f9) - Hide SortChip or SortSection when there is only a single filter option. ([@Renzovh](https://github.com/Renzovh))
8
+
9
+ ## 9.0.0-canary.58
10
+
11
+ ### Patch Changes
12
+
13
+ - [#2328](https://github.com/graphcommerce-org/graphcommerce/pull/2328) [`ee04368`](https://github.com/graphcommerce-org/graphcommerce/commit/ee04368444f732e5541a595db6e2ef66d15add68) - Move to attributesList to get a list of filterable attributes instead of using an introspection query. `productFiltersProSectionRenderer` and `productFiltersProChipRenderer` keys now now one of `AttributeFrontendInputEnum`. ([@paales](https://github.com/paales))
14
+
3
15
  ## 9.0.0-canary.57
4
16
 
5
17
  ## 9.0.0-canary.56
@@ -1,3 +1,4 @@
1
+ import { AttributeFrontendInputEnum } from '@graphcommerce/graphql-mesh'
1
2
  import { ProductListFiltersFragment } from '../ProductListFilters/ProductListFilters.gql'
2
3
  import { ProductFilterEqualChip } from './ProductFilterEqualChip'
3
4
  import { ProductFilterEqualSection } from './ProductFilterEqualSection'
@@ -11,20 +12,24 @@ export type FilterProps = {
11
12
  aggregation: NonNullable<NonNullable<ProductListFiltersFragment['aggregations']>[number]>
12
13
  }
13
14
 
14
- export type FilterRenderer = Record<string, React.FC<FilterProps>>
15
+ export type FilterRenderer = Record<AttributeFrontendInputEnum, React.FC<FilterProps>>
15
16
 
16
17
  export type ProductFiltersProAggregationsProps = {
17
- renderer?: FilterRenderer
18
+ renderer?: Partial<FilterRenderer>
18
19
  }
19
20
 
20
- export const productFiltersProSectionRenderer = {
21
- FilterRangeTypeInput: ProductFilterRangeSection,
22
- FilterEqualTypeInput: ProductFilterEqualSection,
21
+ export const productFiltersProSectionRenderer: Partial<FilterRenderer> = {
22
+ SELECT: ProductFilterEqualSection,
23
+ MULTISELECT: ProductFilterEqualSection,
24
+ BOOLEAN: ProductFilterEqualSection,
25
+ PRICE: ProductFilterRangeSection,
23
26
  }
24
27
 
25
- export const productFiltersProChipRenderer = {
26
- FilterEqualTypeInput: ProductFilterEqualChip,
27
- FilterRangeTypeInput: ProductFilterRangeChip,
28
+ export const productFiltersProChipRenderer: Partial<FilterRenderer> = {
29
+ SELECT: ProductFilterEqualChip,
30
+ MULTISELECT: ProductFilterEqualChip,
31
+ BOOLEAN: ProductFilterEqualChip,
32
+ PRICE: ProductFilterRangeChip,
28
33
  }
29
34
 
30
35
  export function ProductFiltersProAggregations(props: ProductFiltersProAggregationsProps) {
@@ -36,7 +41,10 @@ export function ProductFiltersProAggregations(props: ProductFiltersProAggregatio
36
41
  {excludeCategory(applyAggregationCount(aggregations, appliedAggregations, params)).map(
37
42
  (aggregation) => {
38
43
  const filterType = filterTypes[aggregation.attribute_code]
39
- if (!filterType) return null
44
+ if (!filterType) {
45
+ console.log('Filter not recognized', aggregation.attribute_code, filterTypes)
46
+ return null
47
+ }
40
48
 
41
49
  const Component = renderer?.[filterType]
42
50
  if (!Component) {
@@ -19,6 +19,8 @@ export function ProductFiltersProSortChip(props: ProductListActionSortProps) {
19
19
  const { submit, form } = useProductFiltersPro()
20
20
  const { options, showReset, selected, selectedLabel } = useProductFiltersProSort(props)
21
21
 
22
+ if ((options.length ?? 0) <= 1) return null
23
+
22
24
  return (
23
25
  <ChipOverlayOrPopper
24
26
  {...rest}
@@ -13,6 +13,8 @@ export function ProductFiltersProSortSection(props: ProductFiltersProSortSection
13
13
  const { form } = useProductFiltersPro()
14
14
  const { options, showReset, selected } = useProductFiltersProSort(props)
15
15
 
16
+ if ((options.length ?? 0) <= 1) return null
17
+
16
18
  return (
17
19
  <ActionCardAccordion
18
20
  sx={sx}
@@ -36,7 +36,9 @@ export function useProductFiltersProSort(props: ProductListActionSortProps) {
36
36
 
37
37
  const conf = useQuery(StoreConfigDocument).data?.storeConfig
38
38
  const defaultSortBy = (
39
- category ? category.default_sort_by ?? conf?.catalog_default_sort_by ?? 'position' : 'relevance'
39
+ category
40
+ ? (category.default_sort_by ?? conf?.catalog_default_sort_by ?? 'position')
41
+ : 'relevance'
40
42
  ) as ProductFilterParams['sort']
41
43
 
42
44
  const formSort = useWatch({ control, name: 'sort' })
@@ -1,5 +1,5 @@
1
1
  import { ChipMenuProps } from '@graphcommerce/next-ui'
2
- import { FilterTypes } from '../ProductListItems/filterTypes'
2
+ import { FilterTypes } from '../ProductListItems/getFilterTypes'
3
3
  import { FilterCheckboxType } from './FilterCheckboxType'
4
4
  import { FilterEqualType } from './FilterEqualType'
5
5
  import { FilterRangeType } from './FilterRangeType'
@@ -23,22 +23,16 @@ export function ProductListFilters(props: ProductFiltersProps) {
23
23
  return null
24
24
 
25
25
  switch (filterTypes[aggregation.attribute_code]) {
26
- case 'FilterEqualTypeInput':
27
- if (
28
- aggregation.options?.[0]?.label === '0' ||
29
- aggregation.options?.[1]?.label === '0' ||
30
- aggregation.options?.[0]?.label === '1' ||
31
- aggregation.options?.[1]?.label === '1'
32
- ) {
33
- return (
34
- <FilterCheckboxType
35
- key={aggregation.attribute_code}
36
- {...aggregation}
37
- {...chipMenuProps}
38
- />
39
- )
40
- }
41
-
26
+ case 'BOOLEAN':
27
+ return (
28
+ <FilterCheckboxType
29
+ key={aggregation.attribute_code}
30
+ {...aggregation}
31
+ {...chipMenuProps}
32
+ />
33
+ )
34
+ case 'SELECT':
35
+ case 'MULTISELECT':
42
36
  return (
43
37
  <FilterEqualType
44
38
  key={aggregation.attribute_code}
@@ -46,8 +40,7 @@ export function ProductListFilters(props: ProductFiltersProps) {
46
40
  {...chipMenuProps}
47
41
  />
48
42
  )
49
-
50
- case 'FilterRangeTypeInput':
43
+ case 'PRICE':
51
44
  return (
52
45
  <FilterRangeType
53
46
  key={aggregation.attribute_code}
@@ -56,6 +49,7 @@ export function ProductListFilters(props: ProductFiltersProps) {
56
49
  />
57
50
  )
58
51
  }
52
+
59
53
  // console.log(
60
54
  // 'Filter not recognized',
61
55
  // aggregation.attribute_code,
@@ -0,0 +1,8 @@
1
+ query ProductFilterTypes($filters: AttributeFilterInput!) {
2
+ attributesList(entityType: CATALOG_PRODUCT, filters: $filters) {
3
+ items {
4
+ code
5
+ frontend_input
6
+ }
7
+ }
8
+ }
@@ -67,8 +67,6 @@ export function isFilterTypeRange(filter: AnyFilterType): filter is FilterRangeT
67
67
  )
68
68
  }
69
69
 
70
- export type FilterTypes = Partial<Record<string, string>>
71
-
72
70
  export function toProductListParams(params: ProductFilterParams): ProductListParams {
73
71
  const { sort, dir, filters, ...rest } = params
74
72
 
@@ -1,13 +1,13 @@
1
1
  import type {
2
2
  FilterEqualTypeInput,
3
- FilterMatchTypeInput,
4
3
  FilterRangeTypeInput,
5
4
  SortEnum,
6
5
  } from '@graphcommerce/graphql-mesh'
7
- import { useRouter } from 'next/router'
8
- import { FilterTypes, ProductListParams } from './filterTypes'
9
6
  // eslint-disable-next-line import/no-extraneous-dependencies
10
7
  import { equal } from '@wry/equality'
8
+ import { useRouter } from 'next/router'
9
+ import { ProductListParams } from './filterTypes'
10
+ import { FilterTypes } from './getFilterTypes'
11
11
 
12
12
  export function parseParams(
13
13
  url: string,
@@ -20,7 +20,7 @@ export function parseParams(
20
20
  const typeMap = filterTypes
21
21
 
22
22
  let error = false
23
- query.reduce<string | undefined>((param, value) => {
23
+ query.map(decodeURI).reduce<string | undefined>((param, value) => {
24
24
  // We parse everything in pairs, every second loop we parse
25
25
  if (!param || param === 'q') return value
26
26
 
@@ -44,20 +44,20 @@ export function parseParams(
44
44
 
45
45
  const [from, to] = value.split('-')
46
46
  switch (typeMap[param]) {
47
- case 'FilterMatchTypeInput':
48
- categoryVariables.filters[param] = { match: value } as FilterMatchTypeInput
47
+ case 'BOOLEAN':
48
+ case 'SELECT':
49
+ case 'MULTISELECT':
50
+ categoryVariables.filters[param] = { in: value.split(',') } as FilterEqualTypeInput
49
51
  return undefined
50
- case 'FilterRangeTypeInput':
52
+ case 'PRICE':
51
53
  categoryVariables.filters[param] = {
52
54
  ...(from !== '*' && { from }),
53
55
  ...(to !== '*' && { to }),
54
56
  } as FilterRangeTypeInput
55
57
  return undefined
56
- case 'FilterEqualTypeInput':
57
- categoryVariables.filters[param] = { in: value.split(',') } as FilterEqualTypeInput
58
- return undefined
59
58
  }
60
59
 
60
+ // console.log('Filter not recognized', param, typeMap[param])
61
61
  error = true
62
62
  return undefined
63
63
  }, undefined)
@@ -1,5 +1,7 @@
1
1
  import { gql, ApolloClient, NormalizedCacheObject, TypedDocumentNode } from '@graphcommerce/graphql'
2
- import type { Exact } from '@graphcommerce/graphql-mesh'
2
+ import type { AttributeFrontendInputEnum, Exact } from '@graphcommerce/graphql-mesh'
3
+ import { filterNonNullableKeys, nonNullable } from '@graphcommerce/next-ui'
4
+ import { ProductFilterTypesDocument } from './ProductFilterTypes.gql'
3
5
 
4
6
  type FilterInputTypesQueryVariables = Exact<{ [key: string]: never }>
5
7
 
@@ -25,13 +27,40 @@ const FilterInputTypesDocument = gql`
25
27
  }
26
28
  ` as TypedDocumentNode<FilterInputTypesQuery, FilterInputTypesQueryVariables>
27
29
 
30
+ export type FilterTypes = Partial<Record<string, AttributeFrontendInputEnum>>
31
+
28
32
  export async function getFilterTypes(
29
33
  client: ApolloClient<NormalizedCacheObject>,
30
- ): Promise<Record<string, string | undefined>> {
34
+ isSearch: boolean = false,
35
+ ): Promise<FilterTypes> {
36
+ if (import.meta.graphCommerce.magentoVersion >= 247) {
37
+ const types = await client.query({
38
+ query: ProductFilterTypesDocument,
39
+ variables: {
40
+ filters: isSearch ? { is_filterable_in_search: true } : {},
41
+ },
42
+ })
43
+
44
+ const typeMap: FilterTypes = Object.fromEntries(
45
+ filterNonNullableKeys(types.data.attributesList?.items, ['frontend_input'])
46
+ .map((i) => [i.code, i.frontend_input])
47
+ .filter(nonNullable),
48
+ )
49
+
50
+ return typeMap
51
+ }
52
+
31
53
  const filterInputTypes = await client.query({ query: FilterInputTypesDocument })
32
54
 
33
- const typeMap: Record<string, string | undefined> = Object.fromEntries(
34
- filterInputTypes.data?.__type.inputFields.map(({ name, type }) => [name, type.name]),
55
+ const typeMap: FilterTypes = Object.fromEntries(
56
+ filterInputTypes.data?.__type.inputFields
57
+ .map<[string, AttributeFrontendInputEnum] | undefined>((field) => {
58
+ if (field.type.name === 'FilterEqualTypeInput') return [field.name, 'SELECT']
59
+ if (field.type.name === 'FilterRangeTypeInput') return [field.name, 'PRICE']
60
+ if (field.type.name === 'FilterMatchTypeInput') return [field.name, 'TEXT']
61
+ return undefined
62
+ })
63
+ .filter(nonNullable),
35
64
  )
36
65
 
37
66
  return typeMap
@@ -1,8 +1,8 @@
1
1
  import { cloneDeep, useQuery } from '@graphcommerce/graphql'
2
2
  import { StoreConfigDocument, StoreConfigQuery } from '@graphcommerce/magento-store'
3
+ import { ProductListQueryVariables } from '../ProductList/ProductList.gql'
3
4
  import { CategoryDefaultFragment } from './CategoryDefault.gql'
4
5
  import { ProductListParams } from './filterTypes'
5
- import { ProductListQueryVariables } from '../ProductList/ProductList.gql'
6
6
 
7
7
  export function useProductListApplyCategoryDefaults(
8
8
  params: ProductListParams | undefined,
@@ -66,3 +66,9 @@ export async function productListApplyCategoryDefaults(
66
66
 
67
67
  return newParams
68
68
  }
69
+
70
+ export function categoryDefaultsToProductListFilters(
71
+ variables: ProductListQueryVariables | undefined,
72
+ ): ProductListQueryVariables {
73
+ return { ...variables, filters: { category_uid: variables?.filters?.category_uid } }
74
+ }
@@ -1,6 +1,5 @@
1
1
  import { debounce } from '@graphcommerce/ecommerce-ui'
2
2
  import {
3
- ApolloQueryResult,
4
3
  ApolloClient,
5
4
  useQuery,
6
5
  useInContextQuery,
@@ -9,7 +8,12 @@ import {
9
8
  import { StoreConfigDocument } from '@graphcommerce/magento-store'
10
9
  import { showPageLoadIndicator } from '@graphcommerce/next-ui'
11
10
  import { useEventCallback } from '@mui/material'
12
- import { FilterFormProviderProps, ProductFiltersDocument } from '../components'
11
+ import {
12
+ FilterFormProviderProps,
13
+ ProductFiltersDocument,
14
+ ProductFiltersQuery,
15
+ ProductFiltersQueryVariables,
16
+ } from '../components'
13
17
  import {
14
18
  ProductListDocument,
15
19
  ProductListQuery,
@@ -20,6 +24,7 @@ import { ProductListParams, toProductListParams } from '../components/ProductLis
20
24
  import { useRouterFilterParams } from '../components/ProductListItems/filteredProductList'
21
25
  import {
22
26
  productListApplyCategoryDefaults,
27
+ categoryDefaultsToProductListFilters,
23
28
  useProductListApplyCategoryDefaults,
24
29
  } from '../components/ProductListItems/productListApplyCategoryDefaults'
25
30
 
@@ -30,6 +35,7 @@ type Next = Parameters<NonNullable<FilterFormProviderProps['handleSubmit']>>[1]
30
35
  export const prefetchProductList = debounce(
31
36
  async (
32
37
  variables: ProductListQueryVariables,
38
+ filtersVariables: ProductFiltersQueryVariables,
33
39
  next: Next,
34
40
  client: ApolloClient<any>,
35
41
  shallow: boolean,
@@ -47,8 +53,7 @@ export const prefetchProductList = debounce(
47
53
  const productFilters = client.query({
48
54
  query: ProductFiltersDocument,
49
55
  variables: {
50
- filters: { category_uid: variables.filters?.category_uid },
51
- search: variables.search,
56
+ ...filtersVariables,
52
57
  context,
53
58
  },
54
59
  })
@@ -91,16 +96,23 @@ export const prefetchProductList = debounce(
91
96
  * - Handles customer specific product list queries
92
97
  */
93
98
  export function useProductList<
94
- T extends ProductListQuery & {
95
- params?: ProductListParams
96
- category?: CategoryDefaultFragment | null | undefined
97
- },
99
+ T extends ProductListQuery &
100
+ ProductFiltersQuery & {
101
+ params?: ProductListParams
102
+ category?: CategoryDefaultFragment | null | undefined
103
+ },
98
104
  >(props: T) {
99
105
  const { category } = props
100
106
  const { params, shallow } = useRouterFilterParams(props)
101
107
  const variables = useProductListApplyCategoryDefaults(params, category)
102
108
 
103
109
  const result = useInContextQuery(ProductListDocument, { variables, skip: !shallow }, props)
110
+ const filters = useInContextQuery(
111
+ ProductFiltersDocument,
112
+ { variables: categoryDefaultsToProductListFilters(variables), skip: !shallow },
113
+ props,
114
+ )
115
+
104
116
  const storeConfig = useQuery(StoreConfigDocument).data
105
117
 
106
118
  const handleSubmit: NonNullable<FilterFormProviderProps['handleSubmit']> = useEventCallback(
@@ -115,9 +127,22 @@ export function useProductList<
115
127
 
116
128
  const shallowNow =
117
129
  JSON.stringify(vars.filters?.category_uid) === JSON.stringify(params?.filters.category_uid)
118
- await prefetchProductList(vars, next, result.client, shallowNow)
130
+ await prefetchProductList(
131
+ vars,
132
+ categoryDefaultsToProductListFilters(vars),
133
+ next,
134
+ result.client,
135
+ shallowNow,
136
+ )
119
137
  },
120
138
  )
121
139
 
122
- return { ...props, ...result.data, params, mask: result.mask, handleSubmit }
140
+ return {
141
+ ...props,
142
+ filters: filters.data.filters,
143
+ ...result.data,
144
+ params,
145
+ mask: result.mask,
146
+ handleSubmit,
147
+ }
123
148
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-product",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "9.0.0-canary.57",
5
+ "version": "9.0.0-canary.59",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -18,19 +18,19 @@
18
18
  "typescript": "5.5.3"
19
19
  },
20
20
  "peerDependencies": {
21
- "@graphcommerce/ecommerce-ui": "^9.0.0-canary.57",
22
- "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.57",
23
- "@graphcommerce/framer-next-pages": "^9.0.0-canary.57",
24
- "@graphcommerce/framer-scroller": "^9.0.0-canary.57",
25
- "@graphcommerce/graphql": "^9.0.0-canary.57",
26
- "@graphcommerce/graphql-mesh": "^9.0.0-canary.57",
27
- "@graphcommerce/image": "^9.0.0-canary.57",
28
- "@graphcommerce/magento-cart": "^9.0.0-canary.57",
29
- "@graphcommerce/magento-category": "^9.0.0-canary.57",
30
- "@graphcommerce/magento-store": "^9.0.0-canary.57",
31
- "@graphcommerce/next-ui": "^9.0.0-canary.57",
32
- "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.57",
33
- "@graphcommerce/typescript-config-pwa": "^9.0.0-canary.57",
21
+ "@graphcommerce/ecommerce-ui": "^9.0.0-canary.59",
22
+ "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.59",
23
+ "@graphcommerce/framer-next-pages": "^9.0.0-canary.59",
24
+ "@graphcommerce/framer-scroller": "^9.0.0-canary.59",
25
+ "@graphcommerce/graphql": "^9.0.0-canary.59",
26
+ "@graphcommerce/graphql-mesh": "^9.0.0-canary.59",
27
+ "@graphcommerce/image": "^9.0.0-canary.59",
28
+ "@graphcommerce/magento-cart": "^9.0.0-canary.59",
29
+ "@graphcommerce/magento-category": "^9.0.0-canary.59",
30
+ "@graphcommerce/magento-store": "^9.0.0-canary.59",
31
+ "@graphcommerce/next-ui": "^9.0.0-canary.59",
32
+ "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.59",
33
+ "@graphcommerce/typescript-config-pwa": "^9.0.0-canary.59",
34
34
  "@lingui/core": "^4.2.1",
35
35
  "@lingui/macro": "^4.2.1",
36
36
  "@lingui/react": "^4.2.1",