@faststore/core 2.0.152-alpha.0 → 2.0.154-alpha.0
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/.turbo/turbo-build.log +6 -8
- package/CHANGELOG.md +12 -0
- package/cms/faststore/content-types.json +96 -2
- package/cms/faststore/sections.json +179 -8
- package/package.json +4 -4
- package/src/components/cms/GlobalSections.tsx +3 -25
- package/src/components/cms/RenderSections.tsx +37 -2
- package/src/components/navigation/Navbar/Navbar.tsx +14 -8
- package/src/components/product/ProductGrid/ProductGrid.tsx +8 -3
- package/src/components/search/Filter/Filter.tsx +22 -3
- package/src/components/search/Filter/FilterSlider.tsx +14 -4
- package/src/components/search/SearchDropdown/SearchDropdown.tsx +17 -7
- package/src/components/search/SearchInput/SearchInput.tsx +10 -5
- package/src/components/search/SearchTop/SearchTop.tsx +17 -3
- package/src/components/search/Sort/Sort.tsx +21 -3
- package/src/components/sections/CrossSellingShelf/CrossSellingShelf.tsx +17 -3
- package/src/components/sections/Navbar/Navbar.tsx +5 -0
- package/src/components/sections/ProductGallery/ProductGallery.tsx +44 -154
- package/src/components/sections/ProductShelf/ProductShelf.tsx +7 -76
- package/src/components/sections/Section/section.scss +0 -7
- package/src/components/ui/ProductGallery/ProductGallery.tsx +220 -0
- package/src/components/{sections → ui}/ProductGallery/ProductGalleryPage.tsx +9 -2
- package/src/components/ui/ProductGallery/index.ts +1 -0
- package/src/components/{sections → ui}/ProductShelf/Overrides.tsx +2 -2
- package/src/components/ui/ProductShelf/ProductShelf.tsx +106 -0
- package/src/components/ui/ProductShelf/index.ts +2 -0
- package/src/pages/[...slug].tsx +35 -24
- package/src/pages/s.tsx +38 -13
- package/src/sdk/search/formatSearchPath.ts +8 -1
- package/src/server/cms.ts +11 -2
- package/.next/BUILD_ID +0 -1
- package/.next/build-manifest.json +0 -132
- package/.next/cache/.tsbuildinfo +0 -1
- package/.next/cache/config.json +0 -7
- package/.next/cache/eslint/.cache_1gneedd +0 -1
- package/.next/cache/next-server.js.nft.json +0 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/export-marker.json +0 -1
- package/.next/images-manifest.json +0 -1
- package/.next/next-server.js.nft.json +0 -1
- package/.next/package.json +0 -1
- package/.next/prerender-manifest.json +0 -1
- package/.next/react-loadable-manifest.json +0 -48
- package/.next/required-server-files.json +0 -1
- package/.next/routes-manifest.json +0 -1
- package/.next/server/chunks/104.js +0 -703
- package/.next/server/chunks/123.js +0 -58
- package/.next/server/chunks/143.js +0 -106
- package/.next/server/chunks/183.js +0 -80
- package/.next/server/chunks/247.js +0 -61
- package/.next/server/chunks/280.js +0 -324
- package/.next/server/chunks/287.js +0 -58
- package/.next/server/chunks/312.js +0 -670
- package/.next/server/chunks/336.js +0 -821
- package/.next/server/chunks/350.js +0 -142
- package/.next/server/chunks/368.js +0 -253
- package/.next/server/chunks/401.js +0 -7241
- package/.next/server/chunks/431.js +0 -7241
- package/.next/server/chunks/502.js +0 -600
- package/.next/server/chunks/557.js +0 -132
- package/.next/server/chunks/576.js +0 -80
- package/.next/server/chunks/597.js +0 -169
- package/.next/server/chunks/608.js +0 -644
- package/.next/server/chunks/644.js +0 -235
- package/.next/server/chunks/664.js +0 -3401
- package/.next/server/chunks/676.js +0 -32
- package/.next/server/chunks/701.js +0 -87
- package/.next/server/chunks/74.js +0 -2674
- package/.next/server/chunks/746.js +0 -225
- package/.next/server/chunks/82.js +0 -371
- package/.next/server/chunks/854.js +0 -72
- package/.next/server/chunks/859.js +0 -959
- package/.next/server/chunks/874.js +0 -487
- package/.next/server/chunks/886.js +0 -120
- package/.next/server/chunks/907.js +0 -1723
- package/.next/server/chunks/98.js +0 -124
- package/.next/server/chunks/font-manifest.json +0 -1
- package/.next/server/font-manifest.json +0 -1
- package/.next/server/middleware-build-manifest.js +0 -1
- package/.next/server/middleware-manifest.json +0 -6
- package/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/.next/server/pages/404.js +0 -393
- package/.next/server/pages/404.js.nft.json +0 -1
- package/.next/server/pages/500.js +0 -395
- package/.next/server/pages/500.js.nft.json +0 -1
- package/.next/server/pages/[...slug].js +0 -718
- package/.next/server/pages/[...slug].js.nft.json +0 -1
- package/.next/server/pages/[slug]/p.js +0 -2458
- package/.next/server/pages/[slug]/p.js.nft.json +0 -1
- package/.next/server/pages/_app.js +0 -368
- package/.next/server/pages/_app.js.nft.json +0 -1
- package/.next/server/pages/_document.js +0 -304
- package/.next/server/pages/_document.js.nft.json +0 -1
- package/.next/server/pages/_error.js +0 -164
- package/.next/server/pages/_error.js.nft.json +0 -1
- package/.next/server/pages/account.js +0 -370
- package/.next/server/pages/account.js.nft.json +0 -1
- package/.next/server/pages/api/graphql.js +0 -315
- package/.next/server/pages/api/graphql.js.nft.json +0 -1
- package/.next/server/pages/api/preview.js +0 -118
- package/.next/server/pages/api/preview.js.nft.json +0 -1
- package/.next/server/pages/checkout.js +0 -370
- package/.next/server/pages/checkout.js.nft.json +0 -1
- package/.next/server/pages/en-US/404.html +0 -81
- package/.next/server/pages/en-US/404.json +0 -1
- package/.next/server/pages/en-US/500.html +0 -81
- package/.next/server/pages/en-US/500.json +0 -1
- package/.next/server/pages/en-US/account.html +0 -81
- package/.next/server/pages/en-US/account.json +0 -1
- package/.next/server/pages/en-US/checkout.html +0 -81
- package/.next/server/pages/en-US/checkout.json +0 -1
- package/.next/server/pages/en-US/login.html +0 -81
- package/.next/server/pages/en-US/login.json +0 -1
- package/.next/server/pages/en-US/s.html +0 -81
- package/.next/server/pages/en-US/s.json +0 -1
- package/.next/server/pages/en-US.html +0 -81
- package/.next/server/pages/en-US.json +0 -1
- package/.next/server/pages/index.js +0 -966
- package/.next/server/pages/index.js.nft.json +0 -1
- package/.next/server/pages/login.js +0 -375
- package/.next/server/pages/login.js.nft.json +0 -1
- package/.next/server/pages/s.js +0 -457
- package/.next/server/pages/s.js.nft.json +0 -1
- package/.next/server/pages-manifest.json +0 -16
- package/.next/server/webpack-api-runtime.js +0 -229
- package/.next/server/webpack-runtime.js +0 -229
- package/.next/static/chunks/143.dd8a556e6957baa1.js +0 -1
- package/.next/static/chunks/170.c17ce564bb568265.js +0 -1
- package/.next/static/chunks/226.b57e10ad9932f88c.js +0 -1
- package/.next/static/chunks/327-43715af4f3fffaf6.js +0 -1
- package/.next/static/chunks/336.0846f48eccce57e4.js +0 -1
- package/.next/static/chunks/366-c0bfd9890048babf.js +0 -1
- package/.next/static/chunks/377-61e89c711b136605.js +0 -1
- package/.next/static/chunks/391-47b923ef44945418.js +0 -1
- package/.next/static/chunks/495.8ffebac98e9475dc.js +0 -1
- package/.next/static/chunks/502.ae82de1669112b15.js +0 -1
- package/.next/static/chunks/545-1d3d08edfd2ee4c6.js +0 -1
- package/.next/static/chunks/597.c5cf3fbeae5ceb63.js +0 -1
- package/.next/static/chunks/651.7142f31ce1e052b3.js +0 -1
- package/.next/static/chunks/741.52f7fb873418346f.js +0 -1
- package/.next/static/chunks/98.97381d2021f86cd9.js +0 -1
- package/.next/static/chunks/framework-dfd14d7ce6600b03.js +0 -1
- package/.next/static/chunks/main-fd466221927468fd.js +0 -1
- package/.next/static/chunks/pages/404-459452495a0df278.js +0 -1
- package/.next/static/chunks/pages/500-008e30c48eceebed.js +0 -1
- package/.next/static/chunks/pages/[...slug]-a351612e92518155.js +0 -1
- package/.next/static/chunks/pages/[slug]/p-fa35dd93d53f75f6.js +0 -1
- package/.next/static/chunks/pages/_app-3e4e7e579cb0681a.js +0 -1
- package/.next/static/chunks/pages/_error-a7a0c1d9bfbb4f38.js +0 -1
- package/.next/static/chunks/pages/account-46263f0c100c3eae.js +0 -1
- package/.next/static/chunks/pages/checkout-20bb3710b24df3b6.js +0 -1
- package/.next/static/chunks/pages/index-49b7ee570f7cc4b7.js +0 -1
- package/.next/static/chunks/pages/login-f9d316d261fcc062.js +0 -1
- package/.next/static/chunks/pages/s-890c0c4b88484224.js +0 -1
- package/.next/static/chunks/polyfills-c67a75d1b6f99dc8.js +0 -1
- package/.next/static/chunks/webpack-976c8f7df8e3dea3.js +0 -1
- package/.next/static/css/1323734429a8aa40.css +0 -1
- package/.next/static/css/13a4da555ff5e3be.css +0 -1
- package/.next/static/css/2e00f7ba49c754b3.css +0 -1
- package/.next/static/css/7d822a137c54a781.css +0 -1
- package/.next/static/css/a0feab89b7648c5c.css +0 -1
- package/.next/static/css/a13a9f9cd349d906.css +0 -1
- package/.next/static/css/a45618030b16a245.css +0 -1
- package/.next/static/css/bde408cc006e64f8.css +0 -1
- package/.next/static/css/d462d9478ce00021.css +0 -1
- package/.next/static/css/e02cdad8fc000339.css +0 -1
- package/.next/static/d4K49oW-EfaWNefvNv5AZ/_buildManifest.js +0 -1
- package/.next/static/d4K49oW-EfaWNefvNv5AZ/_ssgManifest.js +0 -1
- package/.next/trace +0 -69
- package/public/~partytown/debug/partytown-atomics.js +0 -556
- package/public/~partytown/debug/partytown-media.js +0 -374
- package/public/~partytown/debug/partytown-sandbox-sw.js +0 -543
- package/public/~partytown/debug/partytown-sw.js +0 -59
- package/public/~partytown/debug/partytown-ww-atomics.js +0 -1789
- package/public/~partytown/debug/partytown-ww-sw.js +0 -1781
- package/public/~partytown/debug/partytown.js +0 -72
- package/public/~partytown/partytown-atomics.js +0 -2
- package/public/~partytown/partytown-media.js +0 -2
- package/public/~partytown/partytown-sw.js +0 -2
- package/public/~partytown/partytown.js +0 -2
- /package/src/components/{sections → ui}/ProductGallery/useDelayedFacets.ts +0 -0
- /package/src/components/{sections → ui}/ProductGallery/useDelayedPagination.ts +0 -0
- /package/src/components/{sections → ui}/ProductGallery/usePageProducts.ts +0 -0
|
@@ -15,7 +15,7 @@ import type { useFilter } from './useFilter'
|
|
|
15
15
|
|
|
16
16
|
import styles from './section.module.scss'
|
|
17
17
|
|
|
18
|
-
interface
|
|
18
|
+
export interface FilterSliderProps {
|
|
19
19
|
/**
|
|
20
20
|
* ID to find this component in testing tools (e.g.: cypress,
|
|
21
21
|
* testing-library, and jest).
|
|
@@ -29,6 +29,14 @@ interface Props {
|
|
|
29
29
|
* Title for the `Filter` component.
|
|
30
30
|
*/
|
|
31
31
|
title?: string
|
|
32
|
+
/**
|
|
33
|
+
* CMS defined label for the clear button component.
|
|
34
|
+
*/
|
|
35
|
+
clearButtonLabel?: string
|
|
36
|
+
/**
|
|
37
|
+
* CMS defined label for the apply button component.
|
|
38
|
+
*/
|
|
39
|
+
applyButtonLabel?: string
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
function FilterSlider({
|
|
@@ -38,7 +46,9 @@ function FilterSlider({
|
|
|
38
46
|
expanded,
|
|
39
47
|
selected,
|
|
40
48
|
title,
|
|
41
|
-
|
|
49
|
+
clearButtonLabel,
|
|
50
|
+
applyButtonLabel,
|
|
51
|
+
}: FilterSliderProps & ReturnType<typeof useFilter>) {
|
|
42
52
|
const { resetInfiniteScroll, setState, state } = useSearch()
|
|
43
53
|
|
|
44
54
|
return (
|
|
@@ -52,7 +62,7 @@ function FilterSlider({
|
|
|
52
62
|
clearBtnProps={{
|
|
53
63
|
variant: 'secondary',
|
|
54
64
|
onClick: () => dispatch({ type: 'selectFacets', payload: [] }),
|
|
55
|
-
children: 'Clear All',
|
|
65
|
+
children: clearButtonLabel ?? 'Clear All',
|
|
56
66
|
}}
|
|
57
67
|
applyBtnProps={{
|
|
58
68
|
variant: 'primary',
|
|
@@ -65,7 +75,7 @@ function FilterSlider({
|
|
|
65
75
|
page: 0,
|
|
66
76
|
})
|
|
67
77
|
},
|
|
68
|
-
children: 'Apply',
|
|
78
|
+
children: applyButtonLabel ?? 'Apply',
|
|
69
79
|
}}
|
|
70
80
|
onClose={() => {
|
|
71
81
|
dispatch({
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SearchProducts,
|
|
2
3
|
SearchAutoComplete as UISearchAutoComplete,
|
|
3
4
|
SearchAutoCompleteTerm as UISearchAutoCompleteTerm,
|
|
4
5
|
SearchDropdown as UISearchDropdown,
|
|
5
|
-
SearchProducts,
|
|
6
6
|
useSearch,
|
|
7
7
|
} from '@faststore/ui'
|
|
8
8
|
|
|
9
9
|
import { SearchHistory } from '../SearchHistory'
|
|
10
10
|
import { SearchTop } from '../SearchTop'
|
|
11
11
|
|
|
12
|
+
import { SearchState } from '@faststore/sdk'
|
|
13
|
+
import { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
12
14
|
import SearchProductItem from 'src/components/search/SearchProductItem'
|
|
13
15
|
import { formatSearchPath } from 'src/sdk/search/formatSearchPath'
|
|
14
|
-
import { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
15
16
|
|
|
16
|
-
function SearchDropdown({ ...otherProps }) {
|
|
17
|
+
function SearchDropdown({ sort, ...otherProps }) {
|
|
17
18
|
const {
|
|
18
19
|
values: { onSearchSelection, products, term, terms },
|
|
19
20
|
} = useSearch()
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
23
|
<UISearchDropdown {...otherProps}>
|
|
23
|
-
<SearchHistory />
|
|
24
|
-
<SearchTop />
|
|
24
|
+
<SearchHistory sort={sort} />
|
|
25
|
+
<SearchTop sort={sort} />
|
|
25
26
|
<UISearchAutoComplete>
|
|
26
27
|
{terms?.map(({ value: suggestion }) => (
|
|
27
28
|
<UISearchAutoCompleteTerm
|
|
@@ -29,9 +30,18 @@ function SearchDropdown({ ...otherProps }) {
|
|
|
29
30
|
term={term}
|
|
30
31
|
suggestion={suggestion}
|
|
31
32
|
linkProps={{
|
|
32
|
-
href: formatSearchPath(
|
|
33
|
+
href: formatSearchPath({
|
|
34
|
+
term: suggestion,
|
|
35
|
+
sort: sort as SearchState['sort'],
|
|
36
|
+
}),
|
|
33
37
|
onClick: () =>
|
|
34
|
-
onSearchSelection?.(
|
|
38
|
+
onSearchSelection?.(
|
|
39
|
+
suggestion,
|
|
40
|
+
formatSearchPath({
|
|
41
|
+
term: suggestion,
|
|
42
|
+
sort: sort as SearchState['sort'],
|
|
43
|
+
})
|
|
44
|
+
),
|
|
35
45
|
}}
|
|
36
46
|
/>
|
|
37
47
|
))}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type { SearchEvent } from '@faststore/sdk'
|
|
1
|
+
import type { SearchEvent, SearchState } from '@faststore/sdk'
|
|
2
2
|
import { sendAnalyticsEvent } from '@faststore/sdk'
|
|
3
3
|
import type {
|
|
4
4
|
SearchInputFieldProps as UISearchInputFieldProps,
|
|
5
5
|
SearchInputFieldRef as UISearchInputFieldRef,
|
|
6
6
|
} from '@faststore/ui'
|
|
7
7
|
import {
|
|
8
|
+
SearchProviderContextValue,
|
|
8
9
|
SearchInput as UISearchInput,
|
|
9
10
|
SearchInputField as UISearchInputField,
|
|
10
|
-
SearchProviderContextValue,
|
|
11
11
|
} from '@faststore/ui'
|
|
12
12
|
import { useRouter } from 'next/router'
|
|
13
13
|
import type { CSSProperties } from 'react'
|
|
14
14
|
import {
|
|
15
|
+
Suspense,
|
|
15
16
|
forwardRef,
|
|
16
17
|
lazy,
|
|
17
|
-
Suspense,
|
|
18
18
|
useDeferredValue,
|
|
19
19
|
useImperativeHandle,
|
|
20
20
|
useRef,
|
|
@@ -34,6 +34,7 @@ export type SearchInputProps = {
|
|
|
34
34
|
onSearchClick?: () => void
|
|
35
35
|
buttonTestId?: string
|
|
36
36
|
containerStyle?: CSSProperties
|
|
37
|
+
sort?: string
|
|
37
38
|
} & Omit<UISearchInputFieldProps, 'onSubmit'>
|
|
38
39
|
|
|
39
40
|
export type SearchInputRef = UISearchInputFieldRef & {
|
|
@@ -53,6 +54,7 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
53
54
|
onSearchClick,
|
|
54
55
|
buttonTestId = 'fs-search-button',
|
|
55
56
|
containerStyle,
|
|
57
|
+
sort,
|
|
56
58
|
...otherProps
|
|
57
59
|
},
|
|
58
60
|
ref
|
|
@@ -103,7 +105,10 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
103
105
|
placeholder="Search everything at the store"
|
|
104
106
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
105
107
|
onSubmit={(term) => {
|
|
106
|
-
const path = formatSearchPath(
|
|
108
|
+
const path = formatSearchPath({
|
|
109
|
+
term,
|
|
110
|
+
sort: sort as SearchState['sort'],
|
|
111
|
+
})
|
|
107
112
|
|
|
108
113
|
onSearchSelection(term, path)
|
|
109
114
|
router.push(path)
|
|
@@ -115,7 +120,7 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
115
120
|
|
|
116
121
|
{searchDropdownVisible && (
|
|
117
122
|
<Suspense fallback={null}>
|
|
118
|
-
<SearchDropdown />
|
|
123
|
+
<SearchDropdown sort={sort} />
|
|
119
124
|
</Suspense>
|
|
120
125
|
)}
|
|
121
126
|
</UISearchInput>
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '@faststore/ui'
|
|
6
6
|
import type { HTMLAttributes } from 'react'
|
|
7
7
|
|
|
8
|
+
import { SearchState } from '@faststore/sdk'
|
|
8
9
|
import type { StoreSuggestionTerm } from '@generated/graphql'
|
|
9
10
|
import { formatSearchPath } from 'src/sdk/search/formatSearchPath'
|
|
10
11
|
import useTopSearch from 'src/sdk/search/useTopSearch'
|
|
@@ -14,9 +15,13 @@ export interface SearchTopProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
14
15
|
* List of top searched items
|
|
15
16
|
*/
|
|
16
17
|
topTerms?: StoreSuggestionTerm[]
|
|
18
|
+
/**
|
|
19
|
+
* Default sort by value
|
|
20
|
+
*/
|
|
21
|
+
sort?: string
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
function SearchTop({ topTerms, ...otherProps }: SearchTopProps) {
|
|
24
|
+
function SearchTop({ topTerms, sort, ...otherProps }: SearchTopProps) {
|
|
20
25
|
const {
|
|
21
26
|
values: { onSearchSelection },
|
|
22
27
|
} = useSearch()
|
|
@@ -34,9 +39,18 @@ function SearchTop({ topTerms, ...otherProps }: SearchTopProps) {
|
|
|
34
39
|
value={term.value}
|
|
35
40
|
index={index}
|
|
36
41
|
linkProps={{
|
|
37
|
-
href: formatSearchPath(
|
|
42
|
+
href: formatSearchPath({
|
|
43
|
+
term: term.value,
|
|
44
|
+
sort: sort as SearchState['sort'],
|
|
45
|
+
}),
|
|
38
46
|
onClick: () =>
|
|
39
|
-
onSearchSelection?.(
|
|
47
|
+
onSearchSelection?.(
|
|
48
|
+
term.value,
|
|
49
|
+
formatSearchPath({
|
|
50
|
+
term: term.value,
|
|
51
|
+
sort: sort as SearchState['sort'],
|
|
52
|
+
})
|
|
53
|
+
),
|
|
40
54
|
}}
|
|
41
55
|
/>
|
|
42
56
|
))}
|
|
@@ -13,16 +13,34 @@ const OptionsMap = {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const keys = Object.keys(OptionsMap) as Array<keyof typeof OptionsMap>
|
|
16
|
+
export interface SortProps {
|
|
17
|
+
label?: string
|
|
18
|
+
options?: {
|
|
19
|
+
price_desc?: string
|
|
20
|
+
price_asc?: string
|
|
21
|
+
orders_desc?: string
|
|
22
|
+
name_asc?: string
|
|
23
|
+
name_desc?: string
|
|
24
|
+
release_desc?: string
|
|
25
|
+
discount_desc?: string
|
|
26
|
+
score_desc?: string
|
|
27
|
+
}
|
|
28
|
+
}
|
|
16
29
|
|
|
17
|
-
function Sort() {
|
|
30
|
+
function Sort({ label = '', options = OptionsMap }: SortProps) {
|
|
18
31
|
const { state, setState } = useSearch()
|
|
19
32
|
|
|
33
|
+
const optionsMap = Object.keys(options).reduce((acc, currentKey) => {
|
|
34
|
+
acc[currentKey] = options[currentKey] ?? OptionsMap[currentKey]
|
|
35
|
+
return acc
|
|
36
|
+
}, {})
|
|
37
|
+
|
|
20
38
|
return (
|
|
21
39
|
<SelectField
|
|
22
40
|
id="sort-select"
|
|
23
41
|
className="sort / text__title-mini-alt"
|
|
24
|
-
label=
|
|
25
|
-
options={
|
|
42
|
+
label={label}
|
|
43
|
+
options={optionsMap}
|
|
26
44
|
onChange={(e) => {
|
|
27
45
|
const sort = keys[e.target.selectedIndex]
|
|
28
46
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
|
|
3
3
|
import type { ProductDetailsFragment_ProductFragment } from '@generated/graphql'
|
|
4
|
-
|
|
5
|
-
import
|
|
4
|
+
import UIProductShelf from 'src/components/ui/ProductShelf'
|
|
5
|
+
import { useInView } from 'react-intersection-observer'
|
|
6
|
+
import styles from '../ProductShelf/section.module.scss'
|
|
7
|
+
import Section from '../Section'
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
10
|
items: number
|
|
@@ -12,13 +14,25 @@ interface Props {
|
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
const CrossSellingShelf = ({ items, title, context, kind }: Props) => {
|
|
17
|
+
const { ref, inView } = useInView()
|
|
18
|
+
|
|
15
19
|
const selectedFacets = useMemo(
|
|
16
20
|
() => [{ key: kind, value: context.isVariantOf.productGroupID }],
|
|
17
21
|
[kind, context.isVariantOf.productGroupID]
|
|
18
22
|
)
|
|
19
23
|
|
|
20
24
|
return (
|
|
21
|
-
<
|
|
25
|
+
<Section
|
|
26
|
+
className={`${styles.section} section-product-shelf layout__section`}
|
|
27
|
+
ref={ref}
|
|
28
|
+
>
|
|
29
|
+
<UIProductShelf
|
|
30
|
+
inView={inView}
|
|
31
|
+
first={items}
|
|
32
|
+
title={title}
|
|
33
|
+
selectedFacets={selectedFacets}
|
|
34
|
+
/>
|
|
35
|
+
</Section>
|
|
22
36
|
)
|
|
23
37
|
}
|
|
24
38
|
|
|
@@ -14,6 +14,9 @@ export interface NavbarProps {
|
|
|
14
14
|
alt: string
|
|
15
15
|
src: string
|
|
16
16
|
}
|
|
17
|
+
searchInput: {
|
|
18
|
+
sort: string
|
|
19
|
+
}
|
|
17
20
|
signInButton: {
|
|
18
21
|
icon: {
|
|
19
22
|
alt: string
|
|
@@ -50,6 +53,7 @@ export interface NavbarProps {
|
|
|
50
53
|
|
|
51
54
|
function Navbar({
|
|
52
55
|
logo,
|
|
56
|
+
searchInput,
|
|
53
57
|
cartIcon,
|
|
54
58
|
signInButton,
|
|
55
59
|
navigation: {
|
|
@@ -69,6 +73,7 @@ function Navbar({
|
|
|
69
73
|
home={home}
|
|
70
74
|
menu={menu}
|
|
71
75
|
logo={logo}
|
|
76
|
+
searchInput={searchInput}
|
|
72
77
|
cart={cartIcon}
|
|
73
78
|
links={pageLinks}
|
|
74
79
|
signIn={{ button: signInButton }}
|
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
import { useSearch } from '@faststore/sdk'
|
|
2
|
-
import {
|
|
3
|
-
Button as UIButton,
|
|
4
|
-
LinkButton as UILinkButton,
|
|
5
|
-
Skeleton as UISkeleton,
|
|
6
|
-
} from '@faststore/ui'
|
|
7
|
-
import { NextSeo } from 'next-seo'
|
|
8
|
-
import type { MouseEvent } from 'react'
|
|
9
|
-
import { Suspense, lazy } from 'react'
|
|
10
|
-
|
|
11
|
-
import { Icon, useUI } from '@faststore/ui'
|
|
12
|
-
import Filter from 'src/components/search/Filter'
|
|
13
|
-
import Sort from 'src/components/search/Sort'
|
|
14
|
-
import FilterSkeleton from 'src/components/skeletons/FilterSkeleton'
|
|
15
|
-
import ProductGridSkeleton from 'src/components/skeletons/ProductGridSkeleton'
|
|
16
1
|
import { mark } from 'src/sdk/tests/mark'
|
|
17
2
|
|
|
3
|
+
import { ServerCollectionPageQueryQuery } from '@generated/graphql'
|
|
4
|
+
import ProductGallery, {
|
|
5
|
+
ProductGalleryProps,
|
|
6
|
+
} from 'src/components/ui/ProductGallery/ProductGallery'
|
|
7
|
+
import { SearchPageContextType } from 'src/pages/s'
|
|
18
8
|
import Section from '../Section'
|
|
19
9
|
import EmptyGallery from './EmptyGallery'
|
|
20
10
|
import styles from './section.module.scss'
|
|
21
|
-
import { useDelayedFacets } from './useDelayedFacets'
|
|
22
|
-
import { useDelayedPagination } from './useDelayedPagination'
|
|
23
11
|
import { useGalleryQuery } from './useGalleryQuery'
|
|
24
|
-
import { useProductsPrefetch } from './usePageProducts'
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
const GalleryPageSkeleton = <ProductGridSkeleton loading />
|
|
13
|
+
type ProductGalleryContext = ServerCollectionPageQueryQuery['collection']
|
|
28
14
|
|
|
29
|
-
interface
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
export interface ProductGallerySectionProps {
|
|
16
|
+
context?: ProductGalleryContext
|
|
17
|
+
searchTermLabel?: ProductGalleryProps['searchTermLabel']
|
|
18
|
+
totalCountLabel?: ProductGalleryProps['totalCountLabel']
|
|
19
|
+
filter: ProductGalleryProps['filter']
|
|
20
|
+
previousPageButton?: ProductGalleryProps['previousPageButton']
|
|
21
|
+
itemsPerPage?: ProductGalleryProps['itemsPerPage']
|
|
22
|
+
loadMorePageButton?: ProductGalleryProps['loadMorePageButton']
|
|
23
|
+
sortBySelector?: ProductGalleryProps['sortBySelector']
|
|
24
|
+
productCard?: ProductGalleryProps['productCard']
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
const isSearch = (x: any): x is SearchPageContextType =>
|
|
28
|
+
x === undefined || x?.title != undefined || x?.searchTerm != undefined
|
|
29
|
+
const isCollection = (
|
|
30
|
+
x: any
|
|
31
|
+
): x is ServerCollectionPageQueryQuery['collection'] =>
|
|
32
|
+
x?.seo != undefined && x?.seo != null && x?.sku == undefined
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
function ProductGallerySection({
|
|
35
|
+
context,
|
|
36
|
+
...otherProps
|
|
37
|
+
}: ProductGallerySectionProps) {
|
|
38
|
+
const [title, searchTerm] = isSearch(context)
|
|
39
|
+
? [context?.title, context?.searchTerm]
|
|
40
|
+
: isCollection(context)
|
|
41
|
+
? [context?.seo?.title]
|
|
42
|
+
: ['']
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const { data: productGalleryData } = useGalleryQuery()
|
|
45
|
+
const totalCount =
|
|
46
|
+
productGalleryData?.search.products.pageInfo.totalCount ?? 0
|
|
45
47
|
|
|
46
|
-
if (
|
|
48
|
+
if (productGalleryData && totalCount === 0) {
|
|
47
49
|
return (
|
|
48
50
|
<Section
|
|
49
51
|
className={`${styles.section} section-product-gallery layout__content`}
|
|
@@ -59,128 +61,16 @@ function ProductGallery({ title, searchTerm }: Props) {
|
|
|
59
61
|
<Section
|
|
60
62
|
className={`${styles.section} section-product-gallery layout__content-full`}
|
|
61
63
|
>
|
|
62
|
-
<
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Showing results for: <span>{searchTerm}</span>
|
|
70
|
-
</h1>
|
|
71
|
-
</header>
|
|
72
|
-
)}
|
|
73
|
-
<div data-fs-product-listing-content-grid className="layout__content">
|
|
74
|
-
<div data-fs-product-listing-filters>
|
|
75
|
-
<FilterSkeleton loading={facets?.length === 0}>
|
|
76
|
-
<Filter facets={facets} />
|
|
77
|
-
</FilterSkeleton>
|
|
78
|
-
</div>
|
|
79
|
-
<div data-fs-product-listing-results-count data-count={totalCount}>
|
|
80
|
-
<UISkeleton
|
|
81
|
-
data-fs-product-listing-results-count-skeleton
|
|
82
|
-
loading={!data}
|
|
83
|
-
size={{ width: '100%', height: '1.5rem' }}
|
|
84
|
-
>
|
|
85
|
-
<h2 data-testid="total-product-count">{totalCount} Results</h2>
|
|
86
|
-
</UISkeleton>
|
|
87
|
-
</div>
|
|
88
|
-
<div data-fs-product-listing-sort>
|
|
89
|
-
<UISkeleton
|
|
90
|
-
data-fs-product-listing-sort-skeleton
|
|
91
|
-
loading={facets?.length === 0}
|
|
92
|
-
size={{ width: 'auto', height: '1.5rem' }}
|
|
93
|
-
>
|
|
94
|
-
<Sort />
|
|
95
|
-
</UISkeleton>
|
|
96
|
-
<UISkeleton
|
|
97
|
-
data-fs-product-listing-filter-button-skeleton
|
|
98
|
-
loading={facets?.length === 0}
|
|
99
|
-
size={{ width: '6rem', height: '1.5rem' }}
|
|
100
|
-
>
|
|
101
|
-
<UIButton
|
|
102
|
-
variant="tertiary"
|
|
103
|
-
data-testid="open-filter-button"
|
|
104
|
-
icon={<Icon name="FadersHorizontal" width={16} height={16} />}
|
|
105
|
-
iconPosition="left"
|
|
106
|
-
aria-label="Open Filters"
|
|
107
|
-
onClick={openFilter}
|
|
108
|
-
>
|
|
109
|
-
Filters
|
|
110
|
-
</UIButton>
|
|
111
|
-
</UISkeleton>
|
|
112
|
-
</div>
|
|
113
|
-
<div data-fs-product-listing-results>
|
|
114
|
-
{/* Add link to previous page. This helps on SEO */}
|
|
115
|
-
{prev !== false && (
|
|
116
|
-
<div data-fs-product-listing-pagination="top">
|
|
117
|
-
<NextSeo
|
|
118
|
-
additionalLinkTags={[{ rel: 'prev', href: prev.link }]}
|
|
119
|
-
/>
|
|
120
|
-
<UILinkButton
|
|
121
|
-
onClick={(e: MouseEvent<HTMLElement>) => {
|
|
122
|
-
e.currentTarget.blur()
|
|
123
|
-
e.preventDefault()
|
|
124
|
-
addPrevPage()
|
|
125
|
-
}}
|
|
126
|
-
href={prev.link}
|
|
127
|
-
rel="prev"
|
|
128
|
-
variant="secondary"
|
|
129
|
-
iconPosition="left"
|
|
130
|
-
icon={
|
|
131
|
-
<Icon
|
|
132
|
-
name="ArrowLeft"
|
|
133
|
-
width={16}
|
|
134
|
-
height={16}
|
|
135
|
-
weight="bold"
|
|
136
|
-
/>
|
|
137
|
-
}
|
|
138
|
-
>
|
|
139
|
-
Previous Page
|
|
140
|
-
</UILinkButton>
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
{/* Render ALL products */}
|
|
144
|
-
{data ? (
|
|
145
|
-
<Suspense fallback={GalleryPageSkeleton}>
|
|
146
|
-
{pages.map((page) => (
|
|
147
|
-
<GalleryPage
|
|
148
|
-
key={`gallery-page-${page}`}
|
|
149
|
-
page={page}
|
|
150
|
-
title={title}
|
|
151
|
-
/>
|
|
152
|
-
))}
|
|
153
|
-
</Suspense>
|
|
154
|
-
) : (
|
|
155
|
-
GalleryPageSkeleton
|
|
156
|
-
)}
|
|
157
|
-
{/* Add link to next page. This helps on SEO */}
|
|
158
|
-
{next !== false && (
|
|
159
|
-
<div data-fs-product-listing-pagination="bottom">
|
|
160
|
-
<NextSeo
|
|
161
|
-
additionalLinkTags={[{ rel: 'next', href: next.link }]}
|
|
162
|
-
/>
|
|
163
|
-
<UILinkButton
|
|
164
|
-
testId="show-more"
|
|
165
|
-
onClick={(e: MouseEvent<HTMLElement>) => {
|
|
166
|
-
e.currentTarget.blur()
|
|
167
|
-
e.preventDefault()
|
|
168
|
-
addNextPage()
|
|
169
|
-
}}
|
|
170
|
-
href={next.link}
|
|
171
|
-
rel="next"
|
|
172
|
-
variant="secondary"
|
|
173
|
-
>
|
|
174
|
-
Load more products
|
|
175
|
-
</UILinkButton>
|
|
176
|
-
</div>
|
|
177
|
-
)}
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</section>
|
|
64
|
+
<ProductGallery
|
|
65
|
+
title={title}
|
|
66
|
+
searchTerm={searchTerm}
|
|
67
|
+
productGalleryData={productGalleryData}
|
|
68
|
+
totalCount={totalCount}
|
|
69
|
+
{...otherProps}
|
|
70
|
+
/>
|
|
181
71
|
</Section>
|
|
182
72
|
)
|
|
183
73
|
}
|
|
184
74
|
|
|
185
|
-
|
|
186
|
-
export default mark(
|
|
75
|
+
ProductGallerySection.displayName = 'ProductGallery'
|
|
76
|
+
export default mark(ProductGallerySection)
|
|
@@ -1,91 +1,22 @@
|
|
|
1
|
-
import { useEffect, useId, useRef } from 'react'
|
|
2
1
|
import { useInView } from 'react-intersection-observer'
|
|
3
|
-
|
|
4
|
-
import { ProductShelf as UIProductShelf } from '@faststore/ui'
|
|
5
|
-
|
|
6
|
-
import type { ProductsQueryQueryVariables } from '@generated/graphql'
|
|
7
|
-
import ProductShelfSkeleton from 'src/components/skeletons/ProductShelfSkeleton'
|
|
8
|
-
import { useViewItemListEvent } from 'src/sdk/analytics/hooks/useViewItemListEvent'
|
|
9
|
-
import { useProductsQuery } from 'src/sdk/product/useProductsQuery'
|
|
10
|
-
import { textToKebabCase } from 'src/utils/utilities'
|
|
11
|
-
|
|
12
|
-
import Carousel from '../../ui/Carousel'
|
|
13
2
|
import Section from '../Section'
|
|
14
|
-
import { Components } from './Overrides'
|
|
15
|
-
const { ProductCard } = Components
|
|
16
3
|
|
|
17
4
|
import styles from './section.module.scss'
|
|
5
|
+
import ProductShelf, { ProductShelfProps } from 'src/components/ui/ProductShelf'
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function ProductShelf({
|
|
25
|
-
title,
|
|
26
|
-
withDivisor = false,
|
|
27
|
-
...variables
|
|
28
|
-
}: ProductShelfProps) {
|
|
29
|
-
const titleId = textToKebabCase(title)
|
|
30
|
-
const id = useId()
|
|
31
|
-
const viewedOnce = useRef(false)
|
|
7
|
+
function ProductShelfSection({
|
|
8
|
+
...otherProps
|
|
9
|
+
}: Omit<ProductShelfProps, 'inView'>) {
|
|
32
10
|
const { ref, inView } = useInView()
|
|
33
|
-
const products = useProductsQuery(variables)
|
|
34
|
-
const productEdges = products?.edges ?? []
|
|
35
|
-
const aspectRatio = 1
|
|
36
|
-
|
|
37
|
-
const { sendViewItemListEvent } = useViewItemListEvent({
|
|
38
|
-
products: productEdges,
|
|
39
|
-
title,
|
|
40
|
-
page: 0,
|
|
41
|
-
pageSize: 0,
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (inView && !viewedOnce.current && productEdges.length) {
|
|
46
|
-
sendViewItemListEvent()
|
|
47
|
-
|
|
48
|
-
viewedOnce.current = true
|
|
49
|
-
}
|
|
50
|
-
}, [inView, productEdges.length, sendViewItemListEvent])
|
|
51
|
-
|
|
52
|
-
if (products?.edges.length === 0) {
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
55
11
|
|
|
56
12
|
return (
|
|
57
13
|
<Section
|
|
58
|
-
className={`${styles.section} section-product-shelf layout__section
|
|
59
|
-
withDivisor ? 'section__divisor' : ''
|
|
60
|
-
}`}
|
|
14
|
+
className={`${styles.section} section-product-shelf layout__section`}
|
|
61
15
|
ref={ref}
|
|
62
16
|
>
|
|
63
|
-
<
|
|
64
|
-
<ProductShelfSkeleton
|
|
65
|
-
aspectRatio={aspectRatio}
|
|
66
|
-
loading={products === undefined}
|
|
67
|
-
>
|
|
68
|
-
<UIProductShelf>
|
|
69
|
-
<Carousel id={titleId || id}>
|
|
70
|
-
{productEdges.map((product, idx) => (
|
|
71
|
-
<ProductCard
|
|
72
|
-
bordered
|
|
73
|
-
key={`${product.node.id}`}
|
|
74
|
-
product={product.node}
|
|
75
|
-
index={idx + 1}
|
|
76
|
-
aspectRatio={aspectRatio}
|
|
77
|
-
imgProps={{
|
|
78
|
-
width: 216,
|
|
79
|
-
height: 216,
|
|
80
|
-
sizes: '(max-width: 768px) 42vw, 30vw',
|
|
81
|
-
}}
|
|
82
|
-
/>
|
|
83
|
-
))}
|
|
84
|
-
</Carousel>
|
|
85
|
-
</UIProductShelf>
|
|
86
|
-
</ProductShelfSkeleton>
|
|
17
|
+
<ProductShelf inView={inView} {...otherProps} />
|
|
87
18
|
</Section>
|
|
88
19
|
)
|
|
89
20
|
}
|
|
90
21
|
|
|
91
|
-
export default
|
|
22
|
+
export default ProductShelfSection
|