@riosst100/pwa-marketplace 1.3.5 → 1.3.6
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/i18n/en_US.json +1 -0
- package/i18n/id_ID.json +1 -0
- package/package.json +1 -1
- package/src/componentOverrideMapping.js +1 -0
- package/src/components/MegaMenu/customMenuItems.js +138 -0
- package/src/components/MegaMenu/customMenuItems.module.css +31 -0
- package/src/components/ProductListTab/index.js +4 -0
- package/src/components/ProductListTab/productListTab.js +41 -0
- package/src/components/ProductListTab/productListTab.module.css +65 -0
- package/src/components/ProductListTab/productListTab.shimmer.js +24 -0
- package/src/components/SubCategory/subCategory.js +6 -2
- package/src/components/SubCategory/subCategory.module.css +22 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/category.gql.js +49 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryFragments.gql.js +51 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategory.js +227 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/category.module.css +1 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +21 -6
- package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterList.js +6 -6
- package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterList.module.css +8 -0
- package/src/overwrites/venia-ui/lib/components/FilterModal/filterModal.module.css +2 -1
- package/src/overwrites/venia-ui/lib/components/Header/header.js +1 -1
package/i18n/en_US.json
CHANGED
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
"field.optional": "Optional",
|
|
173
173
|
"filterFooter.results": "See Results",
|
|
174
174
|
"filterList.showMore": "Show More",
|
|
175
|
+
"filterList.viewAll": "View All",
|
|
175
176
|
"filterList.showLess": "Show Less",
|
|
176
177
|
"filterModal.action": "Clear all",
|
|
177
178
|
"filterModal.action.clearAll.ariaLabel": "Clear all applied filters",
|
package/i18n/id_ID.json
CHANGED
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
"field.optional": "Optional",
|
|
173
173
|
"filterFooter.results": "See Results",
|
|
174
174
|
"filterList.showMore": "Show More",
|
|
175
|
+
"filterList.viewAll": "Lihat Selengkapnya",
|
|
175
176
|
"filterList.showLess": "Show Less",
|
|
176
177
|
"filterModal.action": "Clear all",
|
|
177
178
|
"filterModal.action.clearAll.ariaLabel": "Clear all applied filters",
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ module.exports = componentOverrideMapping = {
|
|
|
19
19
|
[`@magento/venia-ui/lib/components/FilterModal/FilterList/filterDefault.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterDefault.js',
|
|
20
20
|
[`@magento/venia-ui/lib/RootComponents/Category/categoryContent.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js',
|
|
21
21
|
[`@magento/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js',
|
|
22
|
+
[`@magento/peregrine/lib/talons/RootComponents/Category/useCategory.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategory.js',
|
|
22
23
|
[`@magento/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js',
|
|
23
24
|
[`@magento/peregrine/lib/talons/FilterModal/useFilterList.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/FilterModal/useFilterList.js',
|
|
24
25
|
[`@magento/venia-ui/lib/components/FilterSidebar/filterSidebar.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.js',
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { ChevronDown as ArrowDown } from 'react-feather';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
|
|
6
|
+
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
7
|
+
import { useCustomMenuItems } from '@magento/peregrine/lib/talons/MegaMenu/useCustomMenuItems';
|
|
8
|
+
|
|
9
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
10
|
+
import defaultClasses from './megaMenuItem.module.css';
|
|
11
|
+
import Submenu from './submenu';
|
|
12
|
+
import { ArrowDown2 } from 'iconsax-react';
|
|
13
|
+
import { textPrimary } from '@riosst100/pwa-marketplace/src/theme/vars';
|
|
14
|
+
import cn from 'classnames';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The CustomMenuItems component displays mega menu item
|
|
18
|
+
*
|
|
19
|
+
* @param {MegaMenuCategory} props.category
|
|
20
|
+
* @param {String} props.activeCategoryId - uid of active category
|
|
21
|
+
* @param {int} props.mainNavWidth - width of the main nav. It's used for setting min-width of the submenu
|
|
22
|
+
* @param {function} props.onNavigate - function called when clicking on Link
|
|
23
|
+
*/
|
|
24
|
+
const CustomMenuItems = props => {
|
|
25
|
+
const {
|
|
26
|
+
activeCategoryId,
|
|
27
|
+
category,
|
|
28
|
+
mainNavWidth,
|
|
29
|
+
categoryUrlSuffix,
|
|
30
|
+
subMenuState,
|
|
31
|
+
disableFocus,
|
|
32
|
+
onNavigate,
|
|
33
|
+
handleSubMenuFocus,
|
|
34
|
+
handleClickOutside,
|
|
35
|
+
megaMenuItemClassname,
|
|
36
|
+
titleClassName,
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
40
|
+
const categoryUrl = resourceUrl(
|
|
41
|
+
`/${category.url_path}${categoryUrlSuffix || ''}`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const talonProps = useCustomMenuItems({
|
|
45
|
+
category,
|
|
46
|
+
activeCategoryId,
|
|
47
|
+
subMenuState,
|
|
48
|
+
disableFocus
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
isFocused,
|
|
53
|
+
isActive,
|
|
54
|
+
handleMenuItemFocus,
|
|
55
|
+
handleCloseSubMenu,
|
|
56
|
+
isMenuActive,
|
|
57
|
+
handleKeyDown
|
|
58
|
+
} = talonProps;
|
|
59
|
+
|
|
60
|
+
const itemClassName = isMenuActive
|
|
61
|
+
? classes.megaMenuItem_active
|
|
62
|
+
: classes.megaMenuItem;
|
|
63
|
+
|
|
64
|
+
const children = useMemo(() => {
|
|
65
|
+
return category.children.length ? (
|
|
66
|
+
<Submenu
|
|
67
|
+
isFocused={isFocused}
|
|
68
|
+
subMenuState={subMenuState}
|
|
69
|
+
items={category.children}
|
|
70
|
+
shopByItems={category.shop_by}
|
|
71
|
+
mainNavWidth={mainNavWidth}
|
|
72
|
+
handleCloseSubMenu={handleCloseSubMenu}
|
|
73
|
+
categoryUrlSuffix={categoryUrlSuffix}
|
|
74
|
+
onNavigate={onNavigate}
|
|
75
|
+
/>
|
|
76
|
+
) : null;
|
|
77
|
+
}, [
|
|
78
|
+
category,
|
|
79
|
+
isFocused,
|
|
80
|
+
mainNavWidth,
|
|
81
|
+
subMenuState,
|
|
82
|
+
handleCloseSubMenu,
|
|
83
|
+
categoryUrlSuffix,
|
|
84
|
+
onNavigate
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const maybeDownArrowIcon = category.children.length ? (
|
|
88
|
+
<ArrowDown2 className='ml-2 stroke-current' size="16" color={textPrimary} variant="Outline" />
|
|
89
|
+
) : null;
|
|
90
|
+
|
|
91
|
+
const linkAttributes = category.children.length
|
|
92
|
+
? {
|
|
93
|
+
'aria-label': `Category: ${category.name}. ${category.children.length
|
|
94
|
+
} sub-categories`
|
|
95
|
+
}
|
|
96
|
+
: {};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
className={cn(itemClassName, megaMenuItemClassname)}
|
|
101
|
+
data-cy="MegaMenu-CustomMenuItems-megaMenuItem"
|
|
102
|
+
onMouseEnter={() => {
|
|
103
|
+
handleSubMenuFocus();
|
|
104
|
+
handleMenuItemFocus();
|
|
105
|
+
}}
|
|
106
|
+
onFocus={() => {
|
|
107
|
+
handleSubMenuFocus();
|
|
108
|
+
handleMenuItemFocus();
|
|
109
|
+
}}
|
|
110
|
+
onTouchStart={() => {
|
|
111
|
+
handleSubMenuFocus();
|
|
112
|
+
handleMenuItemFocus();
|
|
113
|
+
}}
|
|
114
|
+
onMouseLeave={e => {
|
|
115
|
+
handleClickOutside(e);
|
|
116
|
+
handleCloseSubMenu();
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<Link
|
|
120
|
+
{...linkAttributes}
|
|
121
|
+
onKeyDown={handleKeyDown}
|
|
122
|
+
className={cn(
|
|
123
|
+
isActive ? classes.megaMenuLinkActive : classes.megaMenuLink,
|
|
124
|
+
titleClassName
|
|
125
|
+
)}
|
|
126
|
+
data-cy="MegaMenu-CustomMenuItems-link"
|
|
127
|
+
to={categoryUrl}
|
|
128
|
+
onClick={onNavigate}
|
|
129
|
+
>
|
|
130
|
+
{category.name}
|
|
131
|
+
{maybeDownArrowIcon}
|
|
132
|
+
</Link>
|
|
133
|
+
{children}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default CustomMenuItems;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.megaMenuItem {
|
|
2
|
+
/* composes: px-3 from global;
|
|
3
|
+
composes: py-0 from global; */
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.megaMenuLink {
|
|
7
|
+
composes: items-center from global;
|
|
8
|
+
composes: inline-flex from global;
|
|
9
|
+
/* min-height: 5rem; */
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.megaMenuLinkActive {
|
|
13
|
+
composes: megaMenuLink;
|
|
14
|
+
composes: underline from global;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.megaMenuItem_active {
|
|
18
|
+
composes: megaMenuItem;
|
|
19
|
+
composes: flex from global;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.arrowDown {
|
|
23
|
+
composes: leading-[0] from global;
|
|
24
|
+
composes: pl-2xs from global;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* TODO @TW: cannot compose */
|
|
28
|
+
.megaMenuItem:hover .megaMenuLink,
|
|
29
|
+
.megaMenuItem:focus .megaMenuLink {
|
|
30
|
+
text-decoration: underline;
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
5
|
+
import defaultClasses from './productListTab.module.css';
|
|
6
|
+
|
|
7
|
+
const ProductListTab = props => {
|
|
8
|
+
|
|
9
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
10
|
+
|
|
11
|
+
const [activeTab, setActiveTab] = useState(null);
|
|
12
|
+
|
|
13
|
+
let allProductClass = classes.firstButton;
|
|
14
|
+
if (!activeTab) {
|
|
15
|
+
allProductClass = classes.activeFirstButton;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className={classes.root} aria-busy="true">
|
|
20
|
+
{/* <FormattedMessage
|
|
21
|
+
id={'searchPage.sortContainer'}
|
|
22
|
+
defaultMessage={'Items sorted by '}
|
|
23
|
+
/>
|
|
24
|
+
<span className={classes.sortText}>
|
|
25
|
+
<FormattedMessage
|
|
26
|
+
id={currentSort.sortId}
|
|
27
|
+
defaultMessage={currentSort.sortText}
|
|
28
|
+
/>
|
|
29
|
+
</span> */}
|
|
30
|
+
<nav>
|
|
31
|
+
<ul className={classes.buttonContainer}>
|
|
32
|
+
<li><button className={allProductClass}>All Products</button></li>
|
|
33
|
+
<li><button className={classes.middleButton}>Pre Order</button></li>
|
|
34
|
+
<li><button className={classes.lastButton}>Auctions</button></li>
|
|
35
|
+
</ul>
|
|
36
|
+
</nav>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default ProductListTab;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
composes: bg-white from global;
|
|
3
|
+
composes: flex from global;
|
|
4
|
+
composes: items-center from global;
|
|
5
|
+
composes: flex-wrap from global;
|
|
6
|
+
width: inherit;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.buttonContainer {
|
|
10
|
+
composes: inline-flex from global;
|
|
11
|
+
align-items: center;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.button {
|
|
15
|
+
composes: flex from global;
|
|
16
|
+
composes: px-4 from global;
|
|
17
|
+
composes: py-2 from global;
|
|
18
|
+
composes: transition-colors from global;
|
|
19
|
+
composes: duration-150 from global;
|
|
20
|
+
composes: border from global;
|
|
21
|
+
composes: border-solid from global;
|
|
22
|
+
composes: leading-normal from global;
|
|
23
|
+
composes: text-[14px] from global;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.activeButton {
|
|
27
|
+
composes: button;
|
|
28
|
+
color: white;
|
|
29
|
+
background-color: #4E31DB;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.activeFirstButton {
|
|
33
|
+
composes: activeButton;
|
|
34
|
+
composes: border-r-0 from global;
|
|
35
|
+
composes: rounded-l-lg from global;
|
|
36
|
+
border-radius: 6px 0px 0px 6px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.middleButton {
|
|
40
|
+
composes: button;
|
|
41
|
+
composes: text-colorDefault from global;
|
|
42
|
+
composes: bg-white from global;
|
|
43
|
+
composes: border-l-0 from global;
|
|
44
|
+
composes: border-gray-100 from global;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.lastButton {
|
|
48
|
+
composes: button;
|
|
49
|
+
composes: text-colorDefault from global;
|
|
50
|
+
composes: bg-white from global;
|
|
51
|
+
composes: border-l-0 from global;
|
|
52
|
+
composes: border-gray-100 from global;
|
|
53
|
+
composes: rounded-r-lg from global;
|
|
54
|
+
border-radius: 0px 6px 6px 0px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.firstButton {
|
|
58
|
+
composes: button;
|
|
59
|
+
composes: text-colorDefault from global;
|
|
60
|
+
composes: bg-white from global;
|
|
61
|
+
composes: border-r-0 from global;
|
|
62
|
+
composes: border-gray-100 from global;
|
|
63
|
+
composes: rounded-l-lg from global;
|
|
64
|
+
border-radius: 6px 0px 0px 6px;
|
|
65
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shape, string } from 'prop-types';
|
|
3
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
4
|
+
|
|
5
|
+
import Shimmer from '@magento/venia-ui/lib/components/Shimmer';
|
|
6
|
+
import defaultClasses from './productListTab.module.css';
|
|
7
|
+
|
|
8
|
+
const ProductListTabShimmer = props => {
|
|
9
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className={classes.root} aria-live="polite" aria-busy="true">
|
|
13
|
+
<Shimmer width={10} />
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
ProductListTabShimmer.propTypes = {
|
|
19
|
+
classes: shape({
|
|
20
|
+
root: string
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default ProductListTabShimmer;
|
|
@@ -2,12 +2,16 @@ import { useSubCategory } from '@riosst100/pwa-marketplace/src/talons/SubCategor
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Link } from 'react-router-dom';
|
|
4
4
|
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
5
|
+
import defaultClasses from './subCategory.module.css';
|
|
6
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
5
7
|
|
|
6
8
|
const SubCategory = props => {
|
|
7
9
|
const { children } = props;
|
|
8
10
|
|
|
9
11
|
const talonProps = useSubCategory({ children });
|
|
10
12
|
|
|
13
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
14
|
+
|
|
11
15
|
const {
|
|
12
16
|
normalizedData
|
|
13
17
|
} = talonProps;
|
|
@@ -20,12 +24,12 @@ const SubCategory = props => {
|
|
|
20
24
|
key={index}
|
|
21
25
|
to={resourceUrl(path)}
|
|
22
26
|
>
|
|
23
|
-
<
|
|
27
|
+
<li className={classes.item}>{text}</li>
|
|
24
28
|
</Link>
|
|
25
29
|
)
|
|
26
30
|
});
|
|
27
31
|
|
|
28
|
-
return subCategory
|
|
32
|
+
return <ul className={classes.root}>{subCategory}</ul>;
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
export default SubCategory;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
composes: flex from global;
|
|
3
|
+
composes: flex-wrap from global;
|
|
4
|
+
composes: mt-3 from global;
|
|
5
|
+
composes: gap-[15px] from global;
|
|
6
|
+
margin-bottom: 30px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.item {
|
|
10
|
+
composes: px-4 from global;
|
|
11
|
+
composes: py-2 from global;
|
|
12
|
+
composes: transition-colors from global;
|
|
13
|
+
composes: duration-150 from global;
|
|
14
|
+
composes: border from global;
|
|
15
|
+
composes: border-solid from global;
|
|
16
|
+
composes: leading-normal from global;
|
|
17
|
+
composes: text-[14px] from global;
|
|
18
|
+
composes: text-colorDefault from global;
|
|
19
|
+
composes: bg-white from global;
|
|
20
|
+
composes: border-gray-100 from global;
|
|
21
|
+
border-radius: 5px;
|
|
22
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
import { CategoryFragment, ProductsFragment } from './categoryFragments.gql';
|
|
4
|
+
|
|
5
|
+
export const GET_CATEGORY = gql`
|
|
6
|
+
query GetCategories(
|
|
7
|
+
$id: String!
|
|
8
|
+
$pageSize: Int!
|
|
9
|
+
$currentPage: Int!
|
|
10
|
+
$filters: ProductAttributeFilterInput!
|
|
11
|
+
$sort: ProductAttributeSortInput
|
|
12
|
+
) {
|
|
13
|
+
categories(filters: { category_uid: { in: [$id] } }) {
|
|
14
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
15
|
+
items {
|
|
16
|
+
uid
|
|
17
|
+
...CategoryFragment
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
products(
|
|
21
|
+
pageSize: $pageSize
|
|
22
|
+
currentPage: $currentPage
|
|
23
|
+
filter: $filters
|
|
24
|
+
sort: $sort
|
|
25
|
+
) {
|
|
26
|
+
...ProductsFragment
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
${CategoryFragment}
|
|
30
|
+
${ProductsFragment}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const GET_FILTER_INPUTS = gql`
|
|
34
|
+
query GetFilterInputsForCategory {
|
|
35
|
+
__type(name: "ProductAttributeFilterInput") {
|
|
36
|
+
inputFields {
|
|
37
|
+
name
|
|
38
|
+
type {
|
|
39
|
+
name
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
getCategoryQuery: GET_CATEGORY,
|
|
48
|
+
getFilterInputsQuery: GET_FILTER_INPUTS
|
|
49
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
export const CategoryFragment = gql`
|
|
4
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
5
|
+
fragment CategoryFragment on CategoryTree {
|
|
6
|
+
uid
|
|
7
|
+
meta_title
|
|
8
|
+
meta_keywords
|
|
9
|
+
meta_description
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
export const ProductsFragment = gql`
|
|
14
|
+
fragment ProductsFragment on Products {
|
|
15
|
+
items {
|
|
16
|
+
id
|
|
17
|
+
uid
|
|
18
|
+
name
|
|
19
|
+
seller {
|
|
20
|
+
name
|
|
21
|
+
}
|
|
22
|
+
price_range {
|
|
23
|
+
maximum_price {
|
|
24
|
+
final_price {
|
|
25
|
+
currency
|
|
26
|
+
value
|
|
27
|
+
}
|
|
28
|
+
regular_price {
|
|
29
|
+
currency
|
|
30
|
+
value
|
|
31
|
+
}
|
|
32
|
+
discount {
|
|
33
|
+
amount_off
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
sku
|
|
38
|
+
small_image {
|
|
39
|
+
url
|
|
40
|
+
}
|
|
41
|
+
stock_status
|
|
42
|
+
rating_summary
|
|
43
|
+
__typename
|
|
44
|
+
url_key
|
|
45
|
+
}
|
|
46
|
+
page_info {
|
|
47
|
+
total_pages
|
|
48
|
+
}
|
|
49
|
+
total_count
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { useLocation } from 'react-router-dom';
|
|
3
|
+
import { useLazyQuery, useQuery } from '@apollo/client';
|
|
4
|
+
|
|
5
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
6
|
+
import { useAppContext } from '@magento/peregrine/lib/context/app';
|
|
7
|
+
import { usePagination } from '@magento/peregrine/lib/hooks/usePagination';
|
|
8
|
+
import { useScrollTopOnChange } from '@magento/peregrine/lib/hooks/useScrollTopOnChange';
|
|
9
|
+
import { useSort } from '@magento/peregrine/lib/hooks/useSort';
|
|
10
|
+
import {
|
|
11
|
+
getFiltersFromSearch,
|
|
12
|
+
getFilterInput
|
|
13
|
+
} from '@magento/peregrine/lib/talons/FilterModal/helpers';
|
|
14
|
+
|
|
15
|
+
import DEFAULT_OPERATIONS from './category.gql';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A [React Hook]{@link https://reactjs.org/docs/hooks-intro.html} that
|
|
19
|
+
* controls the logic for the Category Root Component.
|
|
20
|
+
*
|
|
21
|
+
* @kind function
|
|
22
|
+
*
|
|
23
|
+
* @param {object} props
|
|
24
|
+
* @param {String} props.id - Category uid.
|
|
25
|
+
* @param {GraphQLAST} props.operations.getCategoryQuery - Fetches category using a server query
|
|
26
|
+
* @param {GraphQLAST} props.operations.getFilterInputsQuery - Fetches "allowed" filters using a server query
|
|
27
|
+
* @param {GraphQLAST} props.queries.getStoreConfig - Fetches store configuration using a server query
|
|
28
|
+
*
|
|
29
|
+
* @returns {object} result
|
|
30
|
+
* @returns {object} result.error - Indicates a network error occurred.
|
|
31
|
+
* @returns {object} result.categoryData - Category data.
|
|
32
|
+
* @returns {bool} result.isLoading - Category data loading.
|
|
33
|
+
* @returns {string} result.metaDescription - Category meta description.
|
|
34
|
+
* @returns {object} result.pageControl - Category pagination state.
|
|
35
|
+
* @returns {array} result.sortProps - Category sorting parameters.
|
|
36
|
+
* @returns {number} result.pageSize - Category total pages.
|
|
37
|
+
*/
|
|
38
|
+
export const useCategory = props => {
|
|
39
|
+
const {
|
|
40
|
+
id,
|
|
41
|
+
queries: { getPageSize }
|
|
42
|
+
} = props;
|
|
43
|
+
|
|
44
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
45
|
+
const { getCategoryQuery, getFilterInputsQuery } = operations;
|
|
46
|
+
|
|
47
|
+
const { data: pageSizeData } = useQuery(getPageSize, {
|
|
48
|
+
fetchPolicy: 'cache-and-network',
|
|
49
|
+
nextFetchPolicy: 'cache-first'
|
|
50
|
+
});
|
|
51
|
+
const pageSize = pageSizeData && pageSizeData.storeConfig.grid_per_page;
|
|
52
|
+
|
|
53
|
+
const [paginationValues, paginationApi] = usePagination();
|
|
54
|
+
const { currentPage, totalPages } = paginationValues;
|
|
55
|
+
const { setCurrentPage, setTotalPages } = paginationApi;
|
|
56
|
+
|
|
57
|
+
const sortProps = useSort({ sortFromSearch: false });
|
|
58
|
+
const [currentSort] = sortProps;
|
|
59
|
+
|
|
60
|
+
// Keep track of the sort criteria so we can tell when they change.
|
|
61
|
+
const previousSort = useRef(currentSort);
|
|
62
|
+
|
|
63
|
+
const pageControl = {
|
|
64
|
+
currentPage,
|
|
65
|
+
setPage: setCurrentPage,
|
|
66
|
+
totalPages
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const [
|
|
70
|
+
,
|
|
71
|
+
{
|
|
72
|
+
actions: { setPageLoading }
|
|
73
|
+
}
|
|
74
|
+
] = useAppContext();
|
|
75
|
+
|
|
76
|
+
const [runQuery, queryResponse] = useLazyQuery(getCategoryQuery, {
|
|
77
|
+
fetchPolicy: 'cache-and-network',
|
|
78
|
+
nextFetchPolicy: 'cache-first'
|
|
79
|
+
});
|
|
80
|
+
const {
|
|
81
|
+
called: categoryCalled,
|
|
82
|
+
loading: categoryLoading,
|
|
83
|
+
error,
|
|
84
|
+
data
|
|
85
|
+
} = queryResponse;
|
|
86
|
+
const { search } = useLocation();
|
|
87
|
+
|
|
88
|
+
const isBackgroundLoading = !!data && categoryLoading;
|
|
89
|
+
|
|
90
|
+
// Update the page indicator if the GraphQL query is in flight.
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
setPageLoading(isBackgroundLoading);
|
|
93
|
+
}, [isBackgroundLoading, setPageLoading]);
|
|
94
|
+
|
|
95
|
+
// Keep track of the search terms so we can tell when they change.
|
|
96
|
+
const previousSearch = useRef(search);
|
|
97
|
+
|
|
98
|
+
// Get "allowed" filters by intersection of schema and aggregations
|
|
99
|
+
const {
|
|
100
|
+
called: introspectionCalled,
|
|
101
|
+
data: introspectionData,
|
|
102
|
+
loading: introspectionLoading
|
|
103
|
+
} = useQuery(getFilterInputsQuery);
|
|
104
|
+
|
|
105
|
+
// Create a type map we can reference later to ensure we pass valid args
|
|
106
|
+
// to the graphql query.
|
|
107
|
+
// For example: { category_id: 'FilterEqualTypeInput', price: 'FilterRangeTypeInput' }
|
|
108
|
+
const filterTypeMap = useMemo(() => {
|
|
109
|
+
const typeMap = new Map();
|
|
110
|
+
if (introspectionData) {
|
|
111
|
+
introspectionData.__type.inputFields.forEach(({ name, type }) => {
|
|
112
|
+
typeMap.set(name, type.name);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return typeMap;
|
|
116
|
+
}, [introspectionData]);
|
|
117
|
+
|
|
118
|
+
// Run the category query immediately and whenever its variable values change.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
// Wait until we have the type map to fetch product data.
|
|
121
|
+
if (!filterTypeMap.size || !pageSize) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const filters = getFiltersFromSearch(search);
|
|
126
|
+
|
|
127
|
+
// Construct the filter arg object.
|
|
128
|
+
const newFilters = {};
|
|
129
|
+
filters.forEach((values, key) => {
|
|
130
|
+
newFilters[key] = getFilterInput(values, filterTypeMap.get(key));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Use the category uid for the current category page regardless of the
|
|
134
|
+
// applied filters. Follow-up in PWA-404.
|
|
135
|
+
newFilters['category_uid'] = { eq: id };
|
|
136
|
+
|
|
137
|
+
runQuery({
|
|
138
|
+
variables: {
|
|
139
|
+
currentPage: Number(currentPage),
|
|
140
|
+
id: id,
|
|
141
|
+
filters: newFilters,
|
|
142
|
+
pageSize: Number(pageSize),
|
|
143
|
+
sort: { [currentSort.sortAttribute]: currentSort.sortDirection }
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}, [
|
|
147
|
+
currentPage,
|
|
148
|
+
currentSort,
|
|
149
|
+
filterTypeMap,
|
|
150
|
+
id,
|
|
151
|
+
pageSize,
|
|
152
|
+
runQuery,
|
|
153
|
+
search
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
const totalPagesFromData = data
|
|
157
|
+
? data.products.page_info.total_pages
|
|
158
|
+
: null;
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
setTotalPages(totalPagesFromData);
|
|
162
|
+
return () => {
|
|
163
|
+
setTotalPages(null);
|
|
164
|
+
};
|
|
165
|
+
}, [setTotalPages, totalPagesFromData]);
|
|
166
|
+
|
|
167
|
+
// If we get an error after loading we should try to reset to page 1.
|
|
168
|
+
// If we continue to have errors after that, render an error message.
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (error && !categoryLoading && !data && currentPage !== 1) {
|
|
171
|
+
setCurrentPage(1);
|
|
172
|
+
}
|
|
173
|
+
}, [currentPage, error, categoryLoading, setCurrentPage, data]);
|
|
174
|
+
|
|
175
|
+
// Reset the current page back to one (1) when the search string, filters
|
|
176
|
+
// or sort criteria change.
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
// We don't want to compare page value.
|
|
179
|
+
const prevSearch = new URLSearchParams(previousSearch.current);
|
|
180
|
+
const nextSearch = new URLSearchParams(search);
|
|
181
|
+
prevSearch.delete('page');
|
|
182
|
+
nextSearch.delete('page');
|
|
183
|
+
|
|
184
|
+
if (
|
|
185
|
+
prevSearch.toString() !== nextSearch.toString() ||
|
|
186
|
+
previousSort.current.sortAttribute.toString() !==
|
|
187
|
+
currentSort.sortAttribute.toString() ||
|
|
188
|
+
previousSort.current.sortDirection.toString() !==
|
|
189
|
+
currentSort.sortDirection.toString()
|
|
190
|
+
) {
|
|
191
|
+
// The search term changed.
|
|
192
|
+
setCurrentPage(1, true);
|
|
193
|
+
// And update the ref.
|
|
194
|
+
previousSearch.current = search;
|
|
195
|
+
previousSort.current = currentSort;
|
|
196
|
+
}
|
|
197
|
+
}, [currentSort, previousSearch, search, setCurrentPage]);
|
|
198
|
+
|
|
199
|
+
const categoryData = categoryLoading && !data ? null : data;
|
|
200
|
+
const categoryNotFound =
|
|
201
|
+
!categoryLoading && data && data.categories.items.length === 0;
|
|
202
|
+
const metaDescription =
|
|
203
|
+
data &&
|
|
204
|
+
data.categories.items[0] &&
|
|
205
|
+
data.categories.items[0].meta_description
|
|
206
|
+
? data.categories.items[0].meta_description
|
|
207
|
+
: '';
|
|
208
|
+
|
|
209
|
+
// When only categoryLoading is involved, noProductsFound component flashes for a moment
|
|
210
|
+
const loading =
|
|
211
|
+
(introspectionCalled && !categoryCalled) ||
|
|
212
|
+
(categoryLoading && !data) ||
|
|
213
|
+
introspectionLoading;
|
|
214
|
+
|
|
215
|
+
useScrollTopOnChange(currentPage);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
error,
|
|
219
|
+
categoryData,
|
|
220
|
+
loading,
|
|
221
|
+
metaDescription,
|
|
222
|
+
pageControl,
|
|
223
|
+
sortProps,
|
|
224
|
+
pageSize,
|
|
225
|
+
categoryNotFound
|
|
226
|
+
};
|
|
227
|
+
};
|
|
@@ -23,6 +23,8 @@ import SortedByContainer, {
|
|
|
23
23
|
import defaultClasses from './category.module.css';
|
|
24
24
|
import NoProductsFound from './NoProductsFound';
|
|
25
25
|
import cn from 'classnames';
|
|
26
|
+
import ProductListTab, { ProductListTabShimmer } from '@riosst100/pwa-marketplace/src/components/ProductListTab';
|
|
27
|
+
import SubCategory from '@riosst100/pwa-marketplace/src/components/SubCategory/subCategory';
|
|
26
28
|
|
|
27
29
|
const FilterModal = React.lazy(() => import('@magento/venia-ui/lib/components/FilterModal'));
|
|
28
30
|
const FilterSidebar = React.lazy(() =>
|
|
@@ -52,6 +54,7 @@ const CategoryContent = props => {
|
|
|
52
54
|
categoryDescription,
|
|
53
55
|
filters,
|
|
54
56
|
items,
|
|
57
|
+
children,
|
|
55
58
|
totalCount,
|
|
56
59
|
totalPagesFromData
|
|
57
60
|
} = talonProps;
|
|
@@ -94,12 +97,21 @@ const CategoryContent = props => {
|
|
|
94
97
|
<ProductSortShimmer />
|
|
95
98
|
) : null;
|
|
96
99
|
|
|
100
|
+
const shouldShowProductListTab = totalPagesFromData;
|
|
101
|
+
const shouldShowProductListTabShimmer = !totalPagesFromData && isLoading;
|
|
102
|
+
|
|
97
103
|
const maybeSortContainer = shouldShowSortButtons ? (
|
|
98
104
|
<SortedByContainer currentSort={currentSort} />
|
|
99
105
|
) : shouldShowSortShimmer ? (
|
|
100
106
|
<SortedByContainerShimmer />
|
|
101
107
|
) : null;
|
|
102
108
|
|
|
109
|
+
const maybeProductsTabContainer = shouldShowProductListTab ? (
|
|
110
|
+
<ProductListTab />
|
|
111
|
+
) : shouldShowProductListTabShimmer ? (
|
|
112
|
+
<ProductListTabShimmer />
|
|
113
|
+
) : null;
|
|
114
|
+
|
|
103
115
|
const categoryResultsHeading =
|
|
104
116
|
totalCount > 0 ? (
|
|
105
117
|
<FormattedMessage
|
|
@@ -153,7 +165,6 @@ const CategoryContent = props => {
|
|
|
153
165
|
return (
|
|
154
166
|
<Fragment>
|
|
155
167
|
<Breadcrumbs categoryId={categoryId} />
|
|
156
|
-
{/* <SubCategory children={children} /> */}
|
|
157
168
|
<StoreTitle>{categoryName}</StoreTitle>
|
|
158
169
|
<article className={classes.root} data-cy="CategoryContent-root">
|
|
159
170
|
<div className={classes.categoryHeader}>
|
|
@@ -167,6 +178,7 @@ const CategoryContent = props => {
|
|
|
167
178
|
</h1>
|
|
168
179
|
{categoryDescriptionElement}
|
|
169
180
|
</div>
|
|
181
|
+
<SubCategory children={children} />
|
|
170
182
|
<div className={classes.contentWrapper}>
|
|
171
183
|
<div ref={sidebarRef} className={classes.sidebar}>
|
|
172
184
|
<Suspense fallback={<FilterSidebarShimmer />}>
|
|
@@ -174,6 +186,14 @@ const CategoryContent = props => {
|
|
|
174
186
|
</Suspense>
|
|
175
187
|
</div>
|
|
176
188
|
<div className={classes.categoryContent}>
|
|
189
|
+
<div className={cn(classes.heading)}>
|
|
190
|
+
{maybeProductsTabContainer}
|
|
191
|
+
<div className={classes.headerButtons}>
|
|
192
|
+
{maybeFilterButtons}
|
|
193
|
+
{maybeSortButton}
|
|
194
|
+
</div>
|
|
195
|
+
{maybeSortContainer}
|
|
196
|
+
</div>
|
|
177
197
|
<div className={cn(classes.heading)}>
|
|
178
198
|
<div
|
|
179
199
|
data-cy="CategoryContent-categoryInfo"
|
|
@@ -181,11 +201,6 @@ const CategoryContent = props => {
|
|
|
181
201
|
>
|
|
182
202
|
{categoryResultsHeading}
|
|
183
203
|
</div>
|
|
184
|
-
<div className={classes.headerButtons}>
|
|
185
|
-
{maybeFilterButtons}
|
|
186
|
-
{maybeSortButton}
|
|
187
|
-
</div>
|
|
188
|
-
{maybeSortContainer}
|
|
189
204
|
</div>
|
|
190
205
|
{content}
|
|
191
206
|
<Suspense fallback={null}>{filtersModal}</Suspense>
|
|
@@ -62,7 +62,7 @@ const FilterList = props => {
|
|
|
62
62
|
elements1.push(
|
|
63
63
|
<li
|
|
64
64
|
key={key}
|
|
65
|
-
className={classes.
|
|
65
|
+
className={classes.popupFilterItem}
|
|
66
66
|
data-cy="FilterList-item"
|
|
67
67
|
>
|
|
68
68
|
<FilterItem
|
|
@@ -82,7 +82,7 @@ const FilterList = props => {
|
|
|
82
82
|
// return element2;
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
const element2 =
|
|
85
|
+
const element2 = <><ul><div><b>{grouping}</b></div>{elements1}</ul><br /></>;
|
|
86
86
|
|
|
87
87
|
return element2;
|
|
88
88
|
});
|
|
@@ -177,8 +177,8 @@ const FilterList = props => {
|
|
|
177
177
|
defaultMessage: 'Show Less'
|
|
178
178
|
})
|
|
179
179
|
: formatMessage({
|
|
180
|
-
id: 'filterList.
|
|
181
|
-
defaultMessage: 'Show
|
|
180
|
+
id: 'filterList.showAll',
|
|
181
|
+
defaultMessage: 'Show All'
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
return (
|
|
@@ -207,8 +207,8 @@ const FilterList = props => {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
const label = formatMessage({
|
|
210
|
-
id: 'filterList.
|
|
211
|
-
defaultMessage: '
|
|
210
|
+
id: 'filterList.viewAll',
|
|
211
|
+
defaultMessage: 'View All'
|
|
212
212
|
});
|
|
213
213
|
|
|
214
214
|
return (
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
composes: grid from global;
|
|
5
5
|
composes: h-full from global;
|
|
6
6
|
composes: left-0 from global;
|
|
7
|
-
composes: max-w-modal from global;
|
|
7
|
+
/* composes: max-w-modal from global;
|
|
8
|
+
*/
|
|
8
9
|
composes: opacity-0 from global;
|
|
9
10
|
composes: overflow-hidden from global;
|
|
10
11
|
composes: fixed from global;
|