@graphcommerce/magento-recently-viewed-products 7.1.0-canary.41
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 +7 -0
- package/Config.graphqls +20 -0
- package/README.md +10 -0
- package/components/RecentlyViewedProducts.tsx +43 -0
- package/graphql/RecentlyViewedProducts.graphql +10 -0
- package/graphql/RecentlyViewedProducts.graphqls +12 -0
- package/hooks/index.ts +2 -0
- package/hooks/useRecentlyViewedProducts.tsx +44 -0
- package/hooks/useRecentlyViewedSkus.tsx +24 -0
- package/index.ts +3 -0
- package/package.json +31 -0
- package/plugins/RegisterProductAsRecentlyViewed.tsx +85 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @graphcommerce/magento-recently-viewed-products
|
|
2
|
+
|
|
3
|
+
## 7.1.0-canary.41
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2077](https://github.com/graphcommerce-org/graphcommerce/pull/2077) [`e661106d4`](https://github.com/graphcommerce-org/graphcommerce/commit/e661106d45e51c617533f19b397a812e22b6fc82) - Added recently viewed products hook and render component ([@bramvanderholst](https://github.com/bramvanderholst))
|
package/Config.graphqls
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings for recently viewed products
|
|
3
|
+
"""
|
|
4
|
+
input RecentlyViewedProductsConfig {
|
|
5
|
+
"""
|
|
6
|
+
Enable/disable recently viewed products
|
|
7
|
+
"""
|
|
8
|
+
enabled: Boolean
|
|
9
|
+
"""
|
|
10
|
+
Number of recently viewed products to be stored in localStorage
|
|
11
|
+
"""
|
|
12
|
+
maxCount: Int
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
extend input GraphCommerceConfig {
|
|
16
|
+
"""
|
|
17
|
+
Settings for recently viewed products
|
|
18
|
+
"""
|
|
19
|
+
recentlyViewedProducts: RecentlyViewedProductsConfig
|
|
20
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @graphcommerce/magento-recently-viewed-products
|
|
2
|
+
|
|
3
|
+
When visiting a product page, the product SKU is added to a list of recently
|
|
4
|
+
viewed products, stored in the users localStorage.
|
|
5
|
+
|
|
6
|
+
## Configuration
|
|
7
|
+
|
|
8
|
+
When `configurableVariantForSimple` is enabled in `graphcommerce.config.js`,
|
|
9
|
+
fully configured configurable products will be shown as the selected variant in
|
|
10
|
+
recently viewed products.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ProductListItemRenderer, ProductScroller } from '@graphcommerce/magento-product'
|
|
2
|
+
import { useInView } from 'framer-motion'
|
|
3
|
+
import { useRef } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
UseRecentlyViewedProductsProps,
|
|
6
|
+
useRecentlyViewedProducts,
|
|
7
|
+
useRecentlyViewedSkus,
|
|
8
|
+
} from '../hooks'
|
|
9
|
+
|
|
10
|
+
export type RecentlyViewedProductsProps = UseRecentlyViewedProductsProps & {
|
|
11
|
+
title?: React.ReactNode
|
|
12
|
+
productListRenderer: ProductListItemRenderer
|
|
13
|
+
loading?: 'lazy' | 'eager'
|
|
14
|
+
}
|
|
15
|
+
export function RecentlyViewedProducts(props: RecentlyViewedProductsProps) {
|
|
16
|
+
const { exclude, title, productListRenderer, loading = 'lazy' } = props
|
|
17
|
+
|
|
18
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
19
|
+
const isInView = useInView(ref, { margin: '300px', once: true })
|
|
20
|
+
const { skus } = useRecentlyViewedSkus({ exclude })
|
|
21
|
+
const productList = useRecentlyViewedProducts({ exclude, skip: !isInView && loading === 'lazy' })
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
!import.meta.graphCommerce.recentlyViewedProducts?.enabled ||
|
|
25
|
+
(!productList.loading && !skus.length)
|
|
26
|
+
) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const loadingProducts = [...Array(skus.length - productList.products.length).keys()].map((i) => ({
|
|
31
|
+
__typename: 'Skeleton' as const,
|
|
32
|
+
uid: i.toString(),
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ProductScroller
|
|
37
|
+
ref={ref}
|
|
38
|
+
productListRenderer={productListRenderer}
|
|
39
|
+
title={title}
|
|
40
|
+
items={[...loadingProducts, ...productList.products]}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
package/hooks/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useQuery } from '@graphcommerce/graphql'
|
|
2
|
+
import { ProductListDocument } from '@graphcommerce/magento-product'
|
|
3
|
+
import { nonNullable } from '@graphcommerce/next-ui'
|
|
4
|
+
import { useRecentlyViewedSkus, UseRecentlyViewedSkusProps } from './useRecentlyViewedSkus'
|
|
5
|
+
|
|
6
|
+
export type UseRecentlyViewedProductsProps = UseRecentlyViewedSkusProps & { skip?: boolean }
|
|
7
|
+
export function useRecentlyViewedProducts(props: UseRecentlyViewedProductsProps) {
|
|
8
|
+
const { exclude, skip = false } = props
|
|
9
|
+
let { skus, loading } = useRecentlyViewedSkus()
|
|
10
|
+
|
|
11
|
+
const productList = useQuery(ProductListDocument, {
|
|
12
|
+
variables: {
|
|
13
|
+
filters: {
|
|
14
|
+
sku: {
|
|
15
|
+
in: skus.map((p) => p.sku).sort(),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
skip: loading || !skus.length || skip,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const productData =
|
|
23
|
+
productList.data?.products?.items || productList.previousData?.products?.items || []
|
|
24
|
+
|
|
25
|
+
if (exclude) {
|
|
26
|
+
skus = skus.filter(
|
|
27
|
+
(item) =>
|
|
28
|
+
item?.sku &&
|
|
29
|
+
!exclude.includes(item.sku) &&
|
|
30
|
+
item?.parentSku &&
|
|
31
|
+
!exclude.includes(item.parentSku),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Sort products based on the time they were viewed. Last viewed item should be the first item in the array
|
|
36
|
+
const products = skus
|
|
37
|
+
.map((sku) => productData.find((p) => (p?.sku || '') === sku.sku))
|
|
38
|
+
.filter(nonNullable)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
products,
|
|
42
|
+
loading: loading || productList.loading,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useQuery } from '@graphcommerce/graphql'
|
|
2
|
+
import { RecentlyViewedProductsDocument } from '../graphql/RecentlyViewedProducts.gql'
|
|
3
|
+
|
|
4
|
+
export type UseRecentlyViewedSkusProps = { exclude?: string[] }
|
|
5
|
+
|
|
6
|
+
export function useRecentlyViewedSkus(props: UseRecentlyViewedSkusProps = {}) {
|
|
7
|
+
const { exclude } = props
|
|
8
|
+
const { data, loading, previousData } = useQuery(RecentlyViewedProductsDocument, {
|
|
9
|
+
skip: !import.meta.graphCommerce.recentlyViewedProducts?.enabled,
|
|
10
|
+
})
|
|
11
|
+
let skus = (loading ? previousData : data)?.recentlyViewedProducts?.items || []
|
|
12
|
+
|
|
13
|
+
// Filter out excluded products (current product page)
|
|
14
|
+
if (exclude)
|
|
15
|
+
skus = skus.filter(
|
|
16
|
+
(item) =>
|
|
17
|
+
item?.sku &&
|
|
18
|
+
!exclude.includes(item.sku) &&
|
|
19
|
+
item?.parentSku &&
|
|
20
|
+
!exclude.includes(item.parentSku),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return { skus, loading }
|
|
24
|
+
}
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphcommerce/magento-recently-viewed-products",
|
|
3
|
+
"homepage": "https://www.graphcommerce.org/",
|
|
4
|
+
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
+
"version": "7.1.0-canary.41",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
|
+
"eslintConfig": {
|
|
9
|
+
"extends": "@graphcommerce/eslint-config-pwa",
|
|
10
|
+
"parserOptions": {
|
|
11
|
+
"project": "./tsconfig.json"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@graphcommerce/eslint-config-pwa": "7.1.0-canary.41",
|
|
16
|
+
"@graphcommerce/graphql": "7.1.0-canary.41",
|
|
17
|
+
"@graphcommerce/graphql-mesh": "7.1.0-canary.41",
|
|
18
|
+
"@graphcommerce/magento-cart": "7.1.0-canary.41",
|
|
19
|
+
"@graphcommerce/magento-product": "7.1.0-canary.41",
|
|
20
|
+
"@graphcommerce/magento-product-configurable": "7.1.0-canary.41",
|
|
21
|
+
"@graphcommerce/next-config": "7.1.0-canary.41",
|
|
22
|
+
"@graphcommerce/next-ui": "7.1.0-canary.41",
|
|
23
|
+
"@graphcommerce/prettier-config-pwa": "7.1.0-canary.41",
|
|
24
|
+
"@graphcommerce/typescript-config-pwa": "7.1.0-canary.41",
|
|
25
|
+
"@mui/material": "^5.10.16",
|
|
26
|
+
"framer-motion": "^10.0.0",
|
|
27
|
+
"next": "*",
|
|
28
|
+
"react": "^18.2.0",
|
|
29
|
+
"react-dom": "^18.2.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useApolloClient } from '@graphcommerce/graphql'
|
|
2
|
+
import {
|
|
3
|
+
type AddToCartItemSelector,
|
|
4
|
+
type ProductPageMeta,
|
|
5
|
+
ProductPageMetaFragment,
|
|
6
|
+
} from '@graphcommerce/magento-product'
|
|
7
|
+
import { useConfigurableSelectedVariant } from '@graphcommerce/magento-product-configurable/hooks'
|
|
8
|
+
import type { IfConfig, ReactPlugin } from '@graphcommerce/next-config'
|
|
9
|
+
import { useEventCallback } from '@mui/material'
|
|
10
|
+
import { useRouter } from 'next/router'
|
|
11
|
+
import { useEffect } from 'react'
|
|
12
|
+
import { RecentlyViewedProductsDocument } from '../graphql/RecentlyViewedProducts.gql'
|
|
13
|
+
|
|
14
|
+
export const component = 'ProductPageMeta'
|
|
15
|
+
export const exported = '@graphcommerce/magento-product/components/ProductPageMeta/ProductPageMeta'
|
|
16
|
+
export const ifConfig: IfConfig = 'recentlyViewedProducts.enabled'
|
|
17
|
+
|
|
18
|
+
type PluginType = ReactPlugin<typeof ProductPageMeta, AddToCartItemSelector>
|
|
19
|
+
|
|
20
|
+
function ViewHandling(props: { product: ProductPageMetaFragment }) {
|
|
21
|
+
const { product } = props
|
|
22
|
+
const client = useApolloClient()
|
|
23
|
+
const variant = useConfigurableSelectedVariant({ url_key: product?.url_key, index: 0 })
|
|
24
|
+
const { events } = useRouter()
|
|
25
|
+
|
|
26
|
+
const registerView = useEventCallback(async () => {
|
|
27
|
+
const recentlyViewed = await client.query({ query: RecentlyViewedProductsDocument })
|
|
28
|
+
const skus = recentlyViewed.data.recentlyViewedProducts?.items ?? []
|
|
29
|
+
|
|
30
|
+
const isValidVariant =
|
|
31
|
+
(variant?.url_rewrites ?? []).length > 0 &&
|
|
32
|
+
variant?.url_key &&
|
|
33
|
+
import.meta.graphCommerce.configurableVariantForSimple
|
|
34
|
+
|
|
35
|
+
const parentSku = product.sku || ''
|
|
36
|
+
const sku = (isValidVariant ? variant.sku : product.sku) || ''
|
|
37
|
+
|
|
38
|
+
const parentSkuAlreadySet = skus.some(
|
|
39
|
+
(p) => p.sku === product.sku || p.parentSku === product.sku,
|
|
40
|
+
)
|
|
41
|
+
const skuAlreadySet = skus.some((p) => p.sku === sku)
|
|
42
|
+
const skuIsLastItem = skus.length === 0 || skus[skus.length - 1].sku === sku
|
|
43
|
+
|
|
44
|
+
if (skuAlreadySet && skuIsLastItem) return
|
|
45
|
+
|
|
46
|
+
// If SKU already exists in recently viewed products, remove it, so we can re-add it as the first item of the array
|
|
47
|
+
const viewedSkus = [
|
|
48
|
+
...(skuIsLastItem || parentSkuAlreadySet
|
|
49
|
+
? skus.filter((p) => p.sku !== parentSku && p.parentSku !== parentSku)
|
|
50
|
+
: skus),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
// Limit array
|
|
54
|
+
const items = [
|
|
55
|
+
{ __typename: 'RecentlyViewedProduct' as const, parentSku, sku },
|
|
56
|
+
...viewedSkus,
|
|
57
|
+
].splice(0, import.meta.graphCommerce.recentlyViewedProducts?.maxCount || 10)
|
|
58
|
+
|
|
59
|
+
client.writeQuery({
|
|
60
|
+
query: RecentlyViewedProductsDocument,
|
|
61
|
+
broadcast: true,
|
|
62
|
+
data: { recentlyViewedProducts: { __typename: 'RecentlyViewedProducts', items } },
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
events.on('routeChangeStart', registerView)
|
|
68
|
+
return () => events.off('routeChangeStart', registerView)
|
|
69
|
+
}, [events, registerView])
|
|
70
|
+
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const RegisterProductAsRecentlyViewed: PluginType = (props) => {
|
|
75
|
+
const { Prev, product } = props
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<ViewHandling product={product} />
|
|
80
|
+
<Prev {...props} />
|
|
81
|
+
</>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const Plugin = RegisterProductAsRecentlyViewed
|