@riosst100/pwa-marketplace 1.5.4 → 1.5.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.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/src/componentOverrideMapping.js +1 -0
  3. package/src/components/CollectibleGameSets/collectibleGameSets.js +37 -8
  4. package/src/components/CollectibleGameSets/collectibleGameSets.shimmer.js +50 -0
  5. package/src/components/CollectibleGameSets/index.js +1 -0
  6. package/src/components/CustomSortBy/customSortBy.js +198 -0
  7. package/src/components/CustomSortBy/customSortBy.module.css +68 -0
  8. package/src/components/CustomSortBy/customSortItem.js +57 -0
  9. package/src/components/CustomSortBy/customSortItem.module.css +23 -0
  10. package/src/components/CustomSortBy/index.js +1 -0
  11. package/src/components/FilterTop/CustomFilters/customFilters.module.css +0 -1
  12. package/src/components/ProductListTab/productListTab.js +1 -1
  13. package/src/components/SubCategory/subCategory.js +24 -6
  14. package/src/overwrites/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js +252 -0
  15. package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryContent.gql.js +3 -0
  16. package/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategoryContent.js +34 -5
  17. package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +31 -9
  18. package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js +30 -5
  19. package/src/overwrites/venia-ui/lib/components/FilterModal/CurrentFilters/currentFilters.js +12 -10
  20. package/src/overwrites/venia-ui/lib/components/FilterModal/FilterList/filterList.js +1 -1
  21. package/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.js +22 -14
  22. package/src/talons/CollectibleGameSets/collectibleGameSets.gql.js +1 -0
  23. package/src/talons/CollectibleGameSets/useCollectibleGameSets.js +17 -13
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@riosst100/pwa-marketplace",
3
3
  "author": "riosst100@gmail.com",
4
- "version": "1.5.4",
4
+ "version": "1.5.6",
5
5
  "main": "src/index.js",
6
6
  "pwa-studio": {
7
7
  "targets": {
@@ -24,6 +24,7 @@ module.exports = componentOverrideMapping = {
24
24
  [`@magento/peregrine/lib/talons/RootComponents/Category/useCategory.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/RootComponents/Category/useCategory.js',
25
25
  [`@magento/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js',
26
26
  [`@magento/peregrine/lib/talons/FilterModal/useFilterList.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/FilterModal/useFilterList.js',
27
+ [`@magento/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js`]: '@riosst100/pwa-marketplace/src/overwrites/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js',
27
28
  [`@magento/venia-ui/lib/components/FilterSidebar/filterSidebar.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/FilterSidebar/filterSidebar.js',
28
29
  [`@magento/venia-ui/lib/components/TextInput/index.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/TextInput/index.js',
29
30
  [`@magento/venia-ui/lib/components/Pagination/index.js`]: '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/Pagination/index.js',
@@ -10,31 +10,33 @@ import defaultClasses from './collectibleGameSets.module.css';
10
10
  import { useStyle } from '@magento/venia-ui/lib/classify';
11
11
  import cn from 'classnames';
12
12
  import Divider from '@riosst100/pwa-marketplace/src/components/Divider';
13
+ import { CollectibleGameSetsShimmer } from '@riosst100/pwa-marketplace/src/components/CollectibleGameSets';
14
+ import ProductSort from '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/ProductSort';
15
+ import CustomSortBy from '@riosst100/pwa-marketplace/src/components/CustomSortBy';
13
16
 
14
17
  const CollectibleGameSets = props => {
15
18
 
16
- const { product_type } = props;
19
+ const { productType } = props;
17
20
 
18
21
  const [active, setActive] = useState('all');
19
22
 
20
23
  const classes = useStyle(defaultClasses);
21
24
 
22
- const talonProps = useCollectibleGameSets({ product_type });
25
+ const talonProps = useCollectibleGameSets({ productType });
23
26
 
24
- const { error, loading, collectibleGameSets, categoryUrlSuffix, categoryUrlKey, productType } = talonProps;
27
+ const { error, loading, collectibleGameSets, categoryUrlSuffix, categoryUrlKey } = talonProps;
25
28
 
26
29
  if (loading && !collectibleGameSets)
27
- return '';
28
- // return <SellerShimmer />;
30
+ return <CollectibleGameSetsShimmer />;
29
31
  if (error && !collectibleGameSets) return <ErrorView />;
30
32
 
31
33
  if (!collectibleGameSets) {
32
34
  return (
33
35
  <h1>
34
36
  <FormattedMessage
35
- id={'seller.notFound'}
37
+ id={'sets.notDataFound'}
36
38
  defaultMessage={
37
- 'Seller Not Found.'
39
+ 'No data found.'
38
40
  }
39
41
  />
40
42
  </h1>
@@ -43,6 +45,8 @@ const CollectibleGameSets = props => {
43
45
 
44
46
  const setsLengthArr = [];
45
47
 
48
+ const groupByYear = [];
49
+
46
50
  const setRelases = collectibleGameSets.map((setRelease, index) => {
47
51
  const { release_type, sets } = setRelease;
48
52
 
@@ -52,7 +56,9 @@ const CollectibleGameSets = props => {
52
56
 
53
57
  if (sets.length) {
54
58
  sets.map((set, index) => {
55
- const { set_name, option_id, set_abbreviation } = set;
59
+ const { set_name, option_id, set_abbreviation, release_year } = set;
60
+
61
+ groupByYear[release_year] = set;
56
62
 
57
63
  const categoryUrl = resourceUrl(
58
64
  `/games/collectible-game/${categoryUrlKey}${categoryUrlSuffix || ''}?card_set[filter]=${set_name},${option_id}`
@@ -79,18 +85,41 @@ const CollectibleGameSets = props => {
79
85
  );
80
86
  });
81
87
 
88
+ console.log(groupByYear)
89
+
82
90
  const handleActive = (val) => {
83
91
  setActive(val);
84
92
  }
85
93
 
86
94
  const alpha = ['#', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
87
95
 
96
+ const [sortBy, setSortBy] = useState({
97
+ sortText: 'All (A-Z)',
98
+ value: 'all'
99
+ });
100
+
101
+ const availableSortBy = [
102
+ {
103
+ 'label': 'All (A-Z)',
104
+ 'value': 'all'
105
+ },
106
+ {
107
+ 'label': 'Newest',
108
+ 'value': 'newest'
109
+ },
110
+ {
111
+ 'label': 'All (Date)',
112
+ 'value': 'all-date'
113
+ }
114
+ ];
115
+
88
116
  return (
89
117
  <>
90
118
  <h1 className='mx-auto relative block text-xl font-bold text-center pt-10 pb-4'>
91
119
  {productType == "sealed-products" ? "Expansion Sets" : "All Sets"}
92
120
  </h1>
93
121
  <div className='border border-gray-100 px-6'>
122
+ <CustomSortBy sortProps={[sortBy, setSortBy]} availableSortMethods={availableSortBy} />
94
123
  {productType != "sealed-products" ? (
95
124
  <>
96
125
  <section className='single_list-indexing-container relative m-auto py-10'>
@@ -0,0 +1,50 @@
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 './collectibleGameSets.module.css';
7
+ import cn from 'classnames';
8
+ import Divider from '@riosst100/pwa-marketplace/src/components/Divider';
9
+
10
+ const CollectibleGameSets = props => {
11
+ const classes = useStyle(defaultClasses, props.classes);
12
+
13
+ return (
14
+ <>
15
+ <h1 className='mx-auto relative block text-xl font-bold text-center pt-10 pb-4'><Shimmer width="25%" height="6vh" /></h1>
16
+ <div className='border border-gray-100 px-6'>
17
+ <center>
18
+ <section className='single_list-indexing-container relative m-auto py-10'>
19
+ <Shimmer width="95%" height="6vh" />
20
+ </section>
21
+ </center>
22
+ <Divider className="mb-5 px-4" />
23
+ <section className='singles-container'>
24
+ <div className={cn('singles-wrapper block -mx-4', classes.singlesWrapper)}>
25
+ <div className='singles_group-wrapper mb-4 px-2 inline-block w-full'>
26
+ <div className='singles_item_group_letter text-xl font-semibold border-b border-gray-100 pb-1 mb-2' ><Shimmer width="95%" height="100vh" /></div>
27
+ </div>
28
+ <div className='singles_group-wrapper mb-4 px-2 inline-block w-full'>
29
+ <div className='singles_item_group_letter text-xl font-semibold border-b border-gray-100 pb-1 mb-2' ><Shimmer width="95%" height="100vh" /></div>
30
+ </div>
31
+ <div className='singles_group-wrapper mb-4 px-2 inline-block w-full'>
32
+ <div className='singles_item_group_letter text-xl font-semibold border-b border-gray-100 pb-1 mb-2' ><Shimmer width="95%" height="100vh" /></div>
33
+ </div>
34
+ <div className='singles_group-wrapper mb-4 px-2 inline-block w-full'>
35
+ <div className='singles_item_group_letter text-xl font-semibold border-b border-gray-100 pb-1 mb-2' ><Shimmer width="95%" height="100vh" /></div>
36
+ </div>
37
+ <div className='singles_group-wrapper mb-4 px-2 inline-block w-full'>
38
+ <div className='singles_item_group_letter text-xl font-semibold border-b border-gray-100 pb-1 mb-2' ><Shimmer width="95%" height="100vh" /></div>
39
+ </div>
40
+ <div className='singles_group-wrapper mb-4 px-2 inline-block w-full'>
41
+ <div className='singles_item_group_letter text-xl font-semibold border-b border-gray-100 pb-1 mb-2' ><Shimmer width="95%" height="100vh" /></div>
42
+ </div>
43
+ </div>
44
+ </section>
45
+ </div>
46
+ </>
47
+ );
48
+ };
49
+
50
+ export default CollectibleGameSets;
@@ -1 +1,2 @@
1
1
  export { default } from './collectibleGameSets';
2
+ export { default as CollectibleGameSetsShimmer } from './collectibleGameSets.shimmer';
@@ -0,0 +1,198 @@
1
+ import React, { useMemo, useCallback } from 'react';
2
+ import { ChevronDown as ArrowDown } from 'react-feather';
3
+ import { FormattedMessage, useIntl } from 'react-intl';
4
+ import { array, arrayOf, shape, string } from 'prop-types';
5
+ import { useDropdown } from '@magento/peregrine/lib/hooks/useDropdown';
6
+
7
+ import { useStyle } from '@magento/venia-ui/lib/classify';
8
+ import SortItem from './customSortItem';
9
+ import defaultClasses from './customSortBy.module.css';
10
+ import Button from '@magento/venia-ui/lib/components/Button';
11
+ // import Icon from '@magento/venia-ui/lib/components/Icon';
12
+ import { ArrowUp2 } from 'iconsax-react';
13
+ import cn from 'classnames';
14
+
15
+ const CustomSortBy = props => {
16
+ const classes = useStyle(defaultClasses, props.classes);
17
+ const { availableSortMethods, sortProps } = props;
18
+ const [currentSort, setSort] = sortProps;
19
+ const { elementRef, expanded, setExpanded } = useDropdown();
20
+ const { formatMessage, locale } = useIntl();
21
+
22
+ const orderSortingList = useCallback(
23
+ list => {
24
+ return list.sort((a, b) => {
25
+ return a.text.localeCompare(b.text, locale, {
26
+ sensitivity: 'base'
27
+ });
28
+ });
29
+ },
30
+ [locale]
31
+ );
32
+
33
+ const sortMethodsFromConfig = availableSortMethods
34
+ ? availableSortMethods
35
+ .map(method => {
36
+ const { value, label } = method;
37
+ if (value !== 'price' && value !== 'position') {
38
+ return {
39
+ id: `sortItem.${value}`,
40
+ text: label,
41
+ attribute: value,
42
+ sortDirection: 'ASC'
43
+ };
44
+ }
45
+ })
46
+ .filter(method => !!method)
47
+ : null;
48
+
49
+ // click event for menu items
50
+ const handleItemClick = useCallback(
51
+ sortAttribute => {
52
+ setSort(prevSort => {
53
+ return {
54
+ sortText: sortAttribute.text,
55
+ value: sortAttribute.attribute,
56
+ };
57
+ });
58
+ setExpanded(false);
59
+ },
60
+ [setExpanded, setSort]
61
+ );
62
+
63
+ const sortElements = useMemo(() => {
64
+ // should be not render item in collapsed mode.
65
+ if (!expanded) {
66
+ return null;
67
+ }
68
+
69
+ const defaultSortMethods = [
70
+ {
71
+ id: 'sortItem.relevance',
72
+ text: formatMessage({
73
+ id: 'sortItem.relevance',
74
+ defaultMessage: 'Best Match'
75
+ }),
76
+ attribute: 'relevance',
77
+ sortDirection: 'DESC'
78
+ },
79
+ {
80
+ id: 'sortItem.priceDesc',
81
+ text: formatMessage({
82
+ id: 'sortItem.priceDesc',
83
+ defaultMessage: 'Price: High to Low'
84
+ }),
85
+ attribute: 'price',
86
+ sortDirection: 'DESC'
87
+ },
88
+ {
89
+ id: 'sortItem.priceAsc',
90
+ text: formatMessage({
91
+ id: 'sortItem.priceAsc',
92
+ defaultMessage: 'Price: Low to High'
93
+ }),
94
+ attribute: 'price',
95
+ sortDirection: 'ASC'
96
+ }
97
+ ];
98
+
99
+ const allSortingMethods = sortMethodsFromConfig
100
+ ? sortMethodsFromConfig
101
+ : defaultSortMethods;
102
+
103
+ const itemElements = Array.from(allSortingMethods, sortItem => {
104
+ const { attribute, sortDirection } = sortItem;
105
+ const isActive =
106
+ currentSort.value === attribute;
107
+
108
+ const key = `${attribute}--${sortDirection}`;
109
+ return (
110
+ <li key={key} className={classes.menuItem}>
111
+ <SortItem
112
+ sortItem={sortItem}
113
+ active={isActive}
114
+ onClick={handleItemClick}
115
+ />
116
+ </li>
117
+ );
118
+ });
119
+
120
+ return (
121
+ <div className={classes.menu}>
122
+ <ul className='flex flex-col gap-2'>{itemElements}</ul>
123
+ </div>
124
+ );
125
+ }, [
126
+ classes.menu,
127
+ classes.menuItem,
128
+ currentSort.sortAttribute,
129
+ currentSort.sortDirection,
130
+ currentSort.sortFromSearch,
131
+ expanded,
132
+ formatMessage,
133
+ handleItemClick,
134
+ orderSortingList,
135
+ sortMethodsFromConfig
136
+ ]);
137
+
138
+ // expand or collapse on click
139
+ const handleSortClick = () => {
140
+ setExpanded(!expanded);
141
+ };
142
+
143
+ const handleKeypress = e => {
144
+ if (e.code == 'Enter') {
145
+ setExpanded(expanded);
146
+ }
147
+ };
148
+ const result = expanded
149
+ ? formatMessage({
150
+ id: 'productSort.sortButtonExpanded',
151
+ defaultMessage: 'Sort Button Expanded'
152
+ })
153
+ : formatMessage({
154
+ id: 'productSort.sortButtonCollapsed',
155
+ defaultMessage: 'Sort Button Collapsed'
156
+ });
157
+
158
+ return (
159
+ <div
160
+ ref={elementRef}
161
+ className={classes.root}
162
+ data-cy="CustomSortBy-root"
163
+ aria-busy="false"
164
+ >
165
+ <Button
166
+ priority={'low'}
167
+ classes={{
168
+ root_lowPriority: classes.sortButton
169
+ }}
170
+ onClick={handleSortClick}
171
+ data-cy="CustomSortBy-sortButton"
172
+ onKeyDown={handleKeypress}
173
+ aria-label={result}
174
+ className='border border-gray-100 border-solid rounded-[5px] p-2.5 flex gap-x-[15px]'
175
+ >
176
+ <span className={classes.mobileText}>
177
+ <FormattedMessage
178
+ id={'productSort.sortButton'}
179
+ defaultMessage={'Sort'}
180
+ />
181
+ </span>
182
+ <span className={cn(classes.desktopText, 'flex items-center gap-[15px]')}>
183
+ <span className={classes.sortText}>
184
+ <FormattedMessage
185
+ id={'productSort.sortByButton'}
186
+ defaultMessage={'Sort by'}
187
+ />
188
+ &nbsp;{currentSort.sortText}
189
+ </span>
190
+ <ArrowUp2 size={15} className={cn('text-gray-900 transition-all stroke-[#292D32]', expanded ? 'rotate-0' : 'rotate-180')} />
191
+ </span>
192
+ </Button>
193
+ {sortElements}
194
+ </div>
195
+ );
196
+ };
197
+
198
+ export default CustomSortBy;
@@ -0,0 +1,68 @@
1
+ .root {
2
+ composes: relative from global;
3
+ composes: ml-2xs from global;
4
+ display: flex;
5
+ flex-direction: row-reverse;
6
+ margin-top: 20px;
7
+ }
8
+
9
+ .menu {
10
+ composes: absolute from global;
11
+ composes: bg-clip-padding from global;
12
+ composes: bg-white from global;
13
+ composes: border from global;
14
+ composes: border-solid from global;
15
+ composes: border-gray-100 from global;
16
+ composes: list-none from global;
17
+ composes: mb-0 from global;
18
+ composes: min-w-[10rem] from global;
19
+ composes: mt-0.5 from global;
20
+ composes: mx-0 from global;
21
+ composes: right-0 from global;
22
+ composes: rounded from global;
23
+ composes: shadow-menu from global;
24
+ composes: text-colorDefault from global;
25
+ composes: text-left from global;
26
+ composes: top-[110%] from global;
27
+ composes: z-menu from global;
28
+ composes: p-2.5 from global;
29
+ }
30
+
31
+ .menuItem {
32
+ composes: hover_bg-gray-100 from global;
33
+ composes: rounded-[5px] from global;
34
+ composes: relative from global;
35
+ }
36
+
37
+ .sortButton {
38
+ composes: rounded-[5px] from global;
39
+ composes: p-2.5 from global;
40
+
41
+ composes: min-w-[154px] from global;
42
+ }
43
+
44
+ .desktopText {
45
+ composes: hidden from global;
46
+
47
+ composes: lg_inline-flex from global;
48
+ }
49
+
50
+ .sortText {
51
+ composes: leading-normal from global;
52
+ composes: text-colorDefault from global;
53
+ composes: text-base from global;
54
+ }
55
+
56
+ .desktopIconWrapper {
57
+ transform: translateX(10px);
58
+ }
59
+
60
+ .desktopIcon {
61
+ /* composes: icon from '../Icon/icon.module.css'; */
62
+
63
+ composes: stroke-gray-500 from global;
64
+ }
65
+
66
+ .mobileText {
67
+ composes: lg_hidden from global;
68
+ }
@@ -0,0 +1,57 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Check } from 'react-feather';
3
+ import { bool, func, shape, string } from 'prop-types';
4
+
5
+ import { useStyle } from '@magento/venia-ui/lib/classify';
6
+ import defaultClasses from './customSortItem.module.css';
7
+ import cn from 'classnames';
8
+
9
+ const CustomSortItem = props => {
10
+ const { active, onClick, sortItem } = props;
11
+ const classes = useStyle(defaultClasses, props.classes);
12
+
13
+ const handleClick = useCallback(
14
+ e => {
15
+ // use only left click for selection
16
+ if (e.button === 0) {
17
+ onClick(sortItem);
18
+ }
19
+ },
20
+ [sortItem, onClick]
21
+ );
22
+
23
+ const handleKeyDown = useCallback(
24
+ e => {
25
+ if (e.key === 'Enter' || e.key === ' ') {
26
+ e.preventDefault();
27
+ onClick(sortItem);
28
+ }
29
+ },
30
+ [onClick, sortItem]
31
+ );
32
+
33
+ return (
34
+ <button
35
+ className={cn(classes.root, active ? `before_block before_h-[100%] before_w-[3px] before_bg-blue-700 before_left-[-10px] before_absolute` : '')}
36
+ data-cy={active ? 'CustomSortItem-activeButton' : 'CustomSortItem-button'}
37
+ onMouseDown={handleClick}
38
+ onKeyDown={handleKeyDown}
39
+ >
40
+ <span className={classes.content}>
41
+ <span className={classes.text}>{sortItem.text}</span>
42
+ </span>
43
+ </button>
44
+ );
45
+ };
46
+
47
+ CustomSortItem.propTypes = {
48
+ active: bool,
49
+ classes: shape({
50
+ content: string,
51
+ root: string,
52
+ text: string
53
+ }),
54
+ onClick: func
55
+ };
56
+
57
+ export default CustomSortItem;
@@ -0,0 +1,23 @@
1
+ .root {
2
+ composes: flex from global;
3
+ composes: items-center from global;
4
+ composes: w-full from global;
5
+ }
6
+
7
+ .content {
8
+ composes: items-center from global;
9
+ composes: grid from global;
10
+ composes: grid-cols-[1fr] from global;
11
+ composes: grid-flow-col from global;
12
+ composes: gap-3 from global;
13
+ composes: h-[30px] from global;
14
+ composes: px-3 from global;
15
+ composes: py-0 from global;
16
+ composes: w-full from global;
17
+ }
18
+
19
+ .text {
20
+ composes: text-left from global;
21
+ composes: whitespace-nowrap from global;
22
+ composes: text-base from global;
23
+ }
@@ -0,0 +1 @@
1
+ export { default } from './customSortBy';
@@ -20,5 +20,4 @@
20
20
  composes: bg-white from global;
21
21
  composes: border-gray-100 from global;
22
22
  border-radius: 5px;
23
- composes: shadow-type-1 from global;
24
23
  }
@@ -29,7 +29,7 @@ const ProductListTab = props => {
29
29
  </span> */}
30
30
  <nav>
31
31
  <ul className={classes.buttonContainer}>
32
- <li><button className={allProductClass}>All Products</button></li>
32
+ <li><button className={classes.activeFirstButton}>All Products</button></li>
33
33
  <li><button className={classes.middleButton}>Pre Order</button></li>
34
34
  <li><button className={classes.lastButton}>Auctions</button></li>
35
35
  </ul>
@@ -8,21 +8,39 @@ import { useFilterSidebar } from '@magento/peregrine/lib/talons/FilterSidebar';
8
8
  import CollectibleGameSets from '@riosst100/pwa-marketplace/src/components/CollectibleGameSets/collectibleGameSets';
9
9
 
10
10
  const SubCategory = props => {
11
- // const { children } = props;
11
+ const { children } = props;
12
12
 
13
- // const talonProps = useSubCategory({ children });
13
+ const talonProps = useSubCategory({ children });
14
14
 
15
15
  const classes = useStyle(defaultClasses, props.classes);
16
16
 
17
- const [activeTab, setActiveTab] = useState('singles')
17
+ const {
18
+ normalizedData
19
+ } = talonProps;
20
+
21
+ const subCategory = [];
22
+
23
+ normalizedData && normalizedData.map(({ text, path }, index) => {
24
+ subCategory.push(
25
+ <Link
26
+ key={index}
27
+ to={resourceUrl(path)}
28
+ >
29
+ <li className={classes.item}>{text}</li>
30
+ </Link>
31
+ )
32
+ });
33
+
34
+ return subCategory.length ? <ul className={classes.root}>{subCategory}</ul> : '';
35
+
36
+ // const [activeTab, setActiveTab] = useState('singles')
18
37
 
19
38
  return (
20
39
  <>
21
40
  <ul className={classes.root}>
22
- <li className={classes.item} onClick={() => setActiveTab('singles')}>All Sets</li>
23
- <li className={classes.item} onClick={() => setActiveTab('sealed-products')}>Expansion Sets</li>
41
+ <li className={classes.item}>All Sets</li>
42
+ <li className={classes.item}>Expansion Sets</li>
24
43
  </ul>
25
- {activeTab == "singles" ? <CollectibleGameSets product_type={"singles"} /> : <CollectibleGameSets product_type={"sealed-products"} />}
26
44
  </>
27
45
  )
28
46
  };
@@ -0,0 +1,252 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useQuery } from '@apollo/client';
3
+ import { useHistory, useLocation } from 'react-router-dom';
4
+
5
+ import { useAppContext } from '@magento/peregrine/lib/context/app';
6
+
7
+ import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
8
+ import { useFilterState } from '@magento/peregrine/lib/talons/FilterModal';
9
+ import {
10
+ getSearchFromState,
11
+ getStateFromSearch,
12
+ sortFiltersArray,
13
+ stripHtml
14
+ } from '@magento/peregrine/lib/talons/FilterModal/helpers';
15
+
16
+ import DEFAULT_OPERATIONS from '@magento/peregrine/lib/talons/FilterModal/filterModal.gql';
17
+
18
+ const DRAWER_NAME = 'filter';
19
+
20
+ export const useFilterSidebar = props => {
21
+ const { filters } = props;
22
+
23
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
24
+ const { getFilterInputsQuery } = operations;
25
+
26
+ const [isApplying, setIsApplying] = useState(false);
27
+ const [{ drawer }, { toggleDrawer, closeDrawer }] = useAppContext();
28
+ const [filterState, filterApi] = useFilterState();
29
+ const prevDrawer = useRef(null);
30
+ const isOpen = drawer === DRAWER_NAME;
31
+
32
+ const history = useHistory();
33
+ const { pathname, search } = useLocation();
34
+
35
+ const { data: introspectionData } = useQuery(getFilterInputsQuery);
36
+
37
+ const attributeCodes = useMemo(
38
+ () => filters.map(({ attribute_code }) => attribute_code),
39
+ [filters]
40
+ );
41
+
42
+ // Create a set of disabled filters.
43
+ const DISABLED_FILTERS = useMemo(() => {
44
+ const disabled = new Set();
45
+ // Disable category filtering when not on a search page.
46
+ if (pathname !== '/search.html') {
47
+ // disabled.add('category_id');
48
+ // disabled.add('category_uid');
49
+ }
50
+
51
+ return disabled;
52
+ }, [pathname]);
53
+
54
+ // Get "allowed" filters by intersection of filter attribute codes and
55
+ // schema input field types. This restricts the displayed filters to those
56
+ // that the api will understand.
57
+ const possibleFilters = useMemo(() => {
58
+ const nextFilters = new Set();
59
+ const inputFields = introspectionData
60
+ ? introspectionData.__type.inputFields
61
+ : [];
62
+
63
+ // perform mapping and filtering in the same cycle
64
+ for (const { name } of inputFields) {
65
+ const isValid = attributeCodes.includes(name);
66
+ const isEnabled = !DISABLED_FILTERS.has(name);
67
+
68
+ if (isValid && isEnabled) {
69
+ nextFilters.add(name);
70
+ }
71
+ }
72
+
73
+ return nextFilters;
74
+ }, [DISABLED_FILTERS, attributeCodes, introspectionData]);
75
+
76
+ const isBooleanFilter = options => {
77
+ const optionsString = JSON.stringify(options);
78
+ return (
79
+ options.length <= 2 &&
80
+ (optionsString.includes(
81
+ JSON.stringify({
82
+ __typename: 'AggregationOption',
83
+ label: '0',
84
+ value: '0'
85
+ })
86
+ ) ||
87
+ optionsString.includes(
88
+ JSON.stringify({
89
+ __typename: 'AggregationOption',
90
+ label: '1',
91
+ value: '1'
92
+ })
93
+ ))
94
+ );
95
+ };
96
+
97
+ // iterate over filters once to set up all the collections we need
98
+ const [
99
+ filterNames,
100
+ filterKeys,
101
+ filterItems,
102
+ filterFrontendInput
103
+ ] = useMemo(() => {
104
+ const names = new Map();
105
+ const keys = new Set();
106
+ const frontendInput = new Map();
107
+ const itemsByGroup = new Map();
108
+
109
+ const sortedFilters = sortFiltersArray([...filters]);
110
+
111
+ for (const filter of sortedFilters) {
112
+ const { options, label: name, attribute_code: group } = filter;
113
+
114
+ // If this aggregation is not a possible filter, just back out.
115
+ if (possibleFilters.has(group)) {
116
+ const items = [];
117
+
118
+ // add filter name
119
+ names.set(group, name);
120
+
121
+ // add filter key permutations
122
+ keys.add(`${group}[filter]`);
123
+
124
+ // TODO: Get all frontend input type from gql if other filter input types are needed
125
+ // See https://github.com/magento-commerce/magento2-pwa/pull/26
126
+ if (isBooleanFilter(options)) {
127
+ frontendInput.set(group, 'boolean');
128
+ // add items
129
+ items.push({
130
+ title: 'No',
131
+ value: '0',
132
+ label: name + ':' + 'No'
133
+ });
134
+ items.push({
135
+ title: 'Yes',
136
+ value: '1',
137
+ label: name + ':' + 'Yes'
138
+ });
139
+ } else {
140
+ // Add frontend input type
141
+ frontendInput.set(group, null);
142
+ // add items
143
+ for (const { label, value } of options) {
144
+ items.push({ title: stripHtml(label), value });
145
+ }
146
+ }
147
+ itemsByGroup.set(group, items);
148
+ }
149
+ }
150
+
151
+ return [names, keys, itemsByGroup, frontendInput];
152
+ }, [filters, possibleFilters]);
153
+
154
+ // on apply, write filter state to location
155
+ useEffect(() => {
156
+ if (isApplying) {
157
+ const nextSearch = getSearchFromState(
158
+ search,
159
+ filterKeys,
160
+ filterState
161
+ );
162
+
163
+ // write filter state to history
164
+ history.push({ pathname, search: nextSearch });
165
+
166
+ // mark the operation as complete
167
+ setIsApplying(false);
168
+ }
169
+ }, [filterKeys, filterState, history, isApplying, pathname, search]);
170
+
171
+ const handleOpen = useCallback(() => {
172
+ toggleDrawer(DRAWER_NAME);
173
+ }, [toggleDrawer]);
174
+
175
+ const handleClose = useCallback(() => {
176
+ closeDrawer();
177
+ }, [closeDrawer]);
178
+
179
+ const handleApply = useCallback(() => {
180
+ setIsApplying(true);
181
+ handleClose();
182
+ }, [handleClose]);
183
+
184
+ const handleReset = useCallback(() => {
185
+ filterApi.clear();
186
+ setIsApplying(true);
187
+ }, [filterApi, setIsApplying]);
188
+
189
+ const handleKeyDownActions = useCallback(
190
+ event => {
191
+ // do not handle keyboard actions when the modal is closed
192
+ if (!isOpen) {
193
+ return;
194
+ }
195
+
196
+ switch (event.keyCode) {
197
+ // when "Esc" key fired -> close the modal
198
+ case 27:
199
+ handleClose();
200
+ break;
201
+ }
202
+ },
203
+ [isOpen, handleClose]
204
+ );
205
+
206
+ useEffect(() => {
207
+ const justOpened =
208
+ prevDrawer.current === null && drawer === DRAWER_NAME;
209
+ const justClosed =
210
+ prevDrawer.current === DRAWER_NAME && drawer === null;
211
+
212
+ // on drawer toggle, read filter state from location
213
+ if (justOpened || justClosed) {
214
+ const nextState = getStateFromSearch(
215
+ search,
216
+ filterKeys,
217
+ filterItems
218
+ );
219
+
220
+ filterApi.setItems(nextState);
221
+ }
222
+
223
+ // on drawer close, update the modal visibility state
224
+ if (justClosed) {
225
+ handleClose();
226
+ }
227
+
228
+ prevDrawer.current = drawer;
229
+ }, [drawer, filterApi, filterItems, filterKeys, search, handleClose]);
230
+
231
+ useEffect(() => {
232
+ const nextState = getStateFromSearch(search, filterKeys, filterItems);
233
+
234
+ filterApi.setItems(nextState);
235
+ }, [filterApi, filterItems, filterKeys, search]);
236
+
237
+ return {
238
+ filterApi,
239
+ filterItems,
240
+ filterKeys,
241
+ filterNames,
242
+ filterFrontendInput,
243
+ filterState,
244
+ handleApply,
245
+ handleClose,
246
+ handleKeyDownActions,
247
+ handleOpen,
248
+ handleReset,
249
+ isApplying,
250
+ isOpen
251
+ };
252
+ };
@@ -29,6 +29,9 @@ export const GET_CATEGORY_CONTENT = gql`
29
29
  description
30
30
  url_key
31
31
  url_path
32
+ allowed_filters {
33
+ code
34
+ }
32
35
  custom_landing_page
33
36
  attributes_block {
34
37
  label
@@ -85,12 +85,44 @@ export const useCategoryContent = props => {
85
85
  }
86
86
  }, [categoryId, getSortMethods]);
87
87
 
88
- const filters = filterData ? filterData.products.aggregations : null;
88
+ const rawFilters = filterData ? filterData.products.aggregations : null;
89
89
  const items = data ? data.products.items : placeholderItems;
90
+ const category =
91
+ categoryData && categoryData.categories.items.length
92
+ ? categoryData.categories.items[0]
93
+ : null;
90
94
  const children =
91
95
  categoryData && categoryData.categories.items.length
92
96
  ? categoryData.categories.items[0].children
93
97
  : null;
98
+
99
+ const filters = [];
100
+
101
+ rawFilters && rawFilters.map((filter, index) => {
102
+
103
+ const filterOptions = [];
104
+ let label = filter.label;
105
+ if (filter.attribute_code == "category_uid") {
106
+ children && children.map((category, index) => {
107
+ filterOptions.push({
108
+ 'label': category.name,
109
+ 'value': category.uid
110
+ });
111
+ });
112
+
113
+ label = category ? category.name : label;
114
+ }
115
+
116
+ const newFilter = {
117
+ attribute_code: filter.attribute_code,
118
+ count: filter.count,
119
+ label: label,
120
+ options: filterOptions.length ? filterOptions : filter.options
121
+ };
122
+
123
+ filters.push(newFilter);
124
+ });
125
+
94
126
  const attributesBlock = categoryData && categoryData.categories.items.length
95
127
  ? categoryData.categories.items[0].attributes_block
96
128
  : null;
@@ -102,10 +134,7 @@ export const useCategoryContent = props => {
102
134
  ? data.products.page_info.total_pages
103
135
  : null;
104
136
  const totalCount = data ? data.products.total_count : null;
105
- const category =
106
- categoryData && categoryData.categories.items.length
107
- ? categoryData.categories.items[0]
108
- : null;
137
+
109
138
  const categoryName =
110
139
  categoryData && categoryData.categories.items.length
111
140
  ? categoryData.categories.items[0].name
@@ -94,7 +94,7 @@ const CategoryContent = props => {
94
94
  ) : null;
95
95
 
96
96
  const sidebar = shouldShowFilterButtons ? (
97
- <FilterSidebar filters={filters} />
97
+ <FilterSidebar children={children} filters={filters} allowedFilters={category ? category.allowed_filters : []} />
98
98
  ) : shouldShowFilterShimmer ? (
99
99
  <FilterSidebarShimmer />
100
100
  ) : null;
@@ -174,12 +174,34 @@ const CategoryContent = props => {
174
174
  const categoryTitle = categoryName ? categoryName : <Shimmer width={5} />;
175
175
 
176
176
  const { search } = useLocation();
177
- const activeFilters = getFiltersFromSearch(search);
177
+ const allActiveFilters = getFiltersFromSearch(search);
178
+
179
+ const activeFilters = [];
180
+
181
+ if (allActiveFilters && allActiveFilters.size > 0) {
182
+ allActiveFilters.forEach((value, key) => {
183
+ value.forEach((value) => {
184
+ const filterArr = value.split(',');
185
+
186
+ const label = filterArr[0];
187
+ const optionId = filterArr[1];
188
+ if (key == "card_set") {
189
+ activeFilters.push(
190
+ {
191
+ 'label': label
192
+ }
193
+ )
194
+ }
195
+ })
196
+ });
197
+ }
198
+
199
+ const currentFilter = activeFilters && activeFilters.length ? activeFilters[activeFilters.length - 1].label : '';
178
200
 
179
201
  return (
180
202
  <Fragment>
181
- <Breadcrumbs categoryId={categoryId} />
182
- <StoreTitle>{categoryName}</StoreTitle>
203
+ <Breadcrumbs categoryId={categoryId} currentFilter={activeFilters} />
204
+ <StoreTitle>{currentFilter ? currentFilter : categoryName}</StoreTitle>
183
205
  <article className={classes.root} data-cy="CategoryContent-root">
184
206
  <div className={classes.categoryHeader}>
185
207
  <h1 aria-live="polite" className={classes.title}>
@@ -187,16 +209,16 @@ const CategoryContent = props => {
187
209
  className={classes.categoryTitle}
188
210
  data-cy="CategoryContent-categoryTitle"
189
211
  >
190
- {categoryTitle}
212
+ {currentFilter ? currentFilter : categoryTitle}
191
213
  </span>
192
214
  </h1>
193
215
  {categoryDescriptionElement}
194
216
  </div>
195
- {activeFilters.size <= 0 && category && category.custom_landing_page ? (
217
+ {/* {activeFilters.size <= 0 && category && category.custom_landing_page ? ( */}
196
218
  <>
197
- <SubCategory customLandingPage={category.custom_landing_page} />
219
+ <SubCategory children={children} customLandingPage={category ? category.custom_landing_page : ''} />
198
220
  </>
199
- ) : (
221
+ {/* ) : ( */}
200
222
  <>
201
223
  {/* <SubCategory filters={filters} children={children} /> */}
202
224
  <FilterTop filters={filters} category={category} />
@@ -232,7 +254,7 @@ const CategoryContent = props => {
232
254
  </div>
233
255
  </div>
234
256
  </>
235
- )}
257
+ {/* )} */}
236
258
  </article>
237
259
  </Fragment>
238
260
  );
@@ -10,7 +10,8 @@ import Shimmer from '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/comp
10
10
  import defaultClasses from '@riosst100/pwa-marketplace/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.module.css';
11
11
  import { ArrowRight2 } from 'iconsax-react';
12
12
  import cn from 'classnames';
13
-
13
+ import { getFiltersFromSearch } from '@magento/peregrine/lib/talons/FilterModal/helpers';
14
+ import { useLocation } from 'react-router-dom';
14
15
 
15
16
  /**
16
17
  * Breadcrumbs! Generates a sorted display of category links.
@@ -21,7 +22,7 @@ import cn from 'classnames';
21
22
  const Breadcrumbs = props => {
22
23
  const classes = useStyle(defaultClasses, props.classes);
23
24
 
24
- const { categoryId, currentProduct } = props;
25
+ const { categoryId, currentProduct, currentFilter } = props;
25
26
 
26
27
  const talonProps = useBreadcrumbs({ categoryId });
27
28
 
@@ -69,13 +70,36 @@ const Breadcrumbs = props => {
69
70
  );
70
71
  }
71
72
 
73
+ const filterBreadcrumbsElement = [];
74
+
75
+ currentFilter && currentFilter.length && currentFilter.map((filter, index) => {
76
+ currentProduct ? (
77
+ filterBreadcrumbsElement.push(
78
+ <Link
79
+ className={classes.link}
80
+ to={resourceUrl(currentCategoryPath)}
81
+ onClick={handleClick}
82
+ >
83
+ {filter.label}
84
+ </Link>
85
+ )
86
+ ) : (
87
+ filterBreadcrumbsElement.push(
88
+ <>
89
+ <span className={classes.divider}>{DELIMITER}</span>
90
+ <span className={cn(classes.currentCategory, 'text-blue-700 font-medium')}>{filter.label}</span>
91
+ </>
92
+ )
93
+ );
94
+ });
95
+
72
96
  // If we have a "currentProduct" it means we're on a PDP so we want the last
73
97
  // category text to be a link. If we don't have a "currentProduct" we're on
74
98
  // a category page so it should be regular text.
75
- const currentCategoryLink = currentProduct ? (
99
+ const currentCategoryLink = currentProduct || currentFilter && currentFilter.length ? (
76
100
  <Link
77
101
  className={classes.link}
78
- to={resourceUrl(currentCategoryPath)}
102
+ to={'/'+resourceUrl(currentCategoryPath)}
79
103
  onClick={handleClick}
80
104
  >
81
105
  {currentCategory}
@@ -90,7 +114,7 @@ const Breadcrumbs = props => {
90
114
  <span className={classes.text}>{currentProduct}</span>
91
115
  </Fragment>
92
116
  ) : null;
93
-
117
+
94
118
  return (
95
119
  <div className={classes.root} aria-live="polite" aria-busy="false">
96
120
  <Link className={classes.link} to="/">
@@ -99,6 +123,7 @@ const Breadcrumbs = props => {
99
123
  {links}
100
124
  <span className={classes.divider}>{DELIMITER}</span>
101
125
  {currentCategoryLink}
126
+ {filterBreadcrumbsElement}
102
127
  {currentProductNode}
103
128
  </div>
104
129
  );
@@ -20,16 +20,18 @@ const CurrentFilters = props => {
20
20
  const { title, value } = item || {};
21
21
  const key = `${group}::${title}_${value}`;
22
22
 
23
- elements.push(
24
- <li key={key} className={classes.item}>
25
- <CurrentFilter
26
- group={group}
27
- item={item}
28
- removeItem={removeItem}
29
- onRemove={onRemove}
30
- />
31
- </li>
32
- );
23
+ if (group != "card_set") {
24
+ elements.push(
25
+ <li key={key} className={classes.item}>
26
+ <CurrentFilter
27
+ group={group}
28
+ item={item}
29
+ removeItem={removeItem}
30
+ onRemove={onRemove}
31
+ />
32
+ </li>
33
+ );
34
+ }
33
35
  }
34
36
  }
35
37
 
@@ -245,7 +245,7 @@ const FilterList = props => {
245
245
 
246
246
  FilterList.defaultProps = {
247
247
  onApply: null,
248
- itemCountToShow: 2
248
+ itemCountToShow: 5
249
249
  };
250
250
 
251
251
  FilterList.propTypes = {
@@ -19,7 +19,7 @@ const SCROLL_OFFSET = 150;
19
19
  * @param {Object} props.filters - filters to display
20
20
  */
21
21
  const FilterSidebar = props => {
22
- const { filters, filterCountToOpen } = props;
22
+ const { filters, filterCountToOpen, allowedFilters } = props;
23
23
  const talonProps = useFilterSidebar({ filters });
24
24
  const {
25
25
  filterApi,
@@ -52,25 +52,33 @@ const FilterSidebar = props => {
52
52
  [handleApply, filterRef]
53
53
  );
54
54
 
55
+ const allowedFiltersArr = [];
56
+
57
+ allowedFilters.length && allowedFilters.map((val, index) => {
58
+ allowedFiltersArr.push(val.code);
59
+ });
60
+
55
61
  const filtersList = useMemo(
56
62
  () =>
57
63
  Array.from(filterItems, ([group, items], iteration) => {
58
64
  const blockState = filterState.get(group);
59
65
  const groupName = filterNames.get(group);
60
66
  const frontendInput = filterFrontendInput.get(group);
61
- return (
62
- <FilterBlock
63
- key={group}
64
- filterApi={filterApi}
65
- filterState={blockState}
66
- filterFrontendInput={frontendInput}
67
- group={group}
68
- items={items}
69
- name={groupName}
70
- onApply={handleApplyFilter}
71
- initialOpen={iteration < filterCountToOpen}
72
- />
73
- );
67
+ if (!allowedFiltersArr.length && group != "category_uid" || allowedFiltersArr.length && allowedFiltersArr.includes(group)) {
68
+ return (
69
+ <FilterBlock
70
+ key={group}
71
+ filterApi={filterApi}
72
+ filterState={blockState}
73
+ filterFrontendInput={frontendInput}
74
+ group={group}
75
+ items={items}
76
+ name={groupName}
77
+ onApply={handleApplyFilter}
78
+ initialOpen={iteration < filterCountToOpen}
79
+ />
80
+ );
81
+ }
74
82
  }),
75
83
  [
76
84
  filterApi,
@@ -20,6 +20,7 @@ export const GET_COLLECTIBLE_GAME_QUERY = gql`
20
20
  option_id
21
21
  set_abbreviation
22
22
  release_date
23
+ release_year
23
24
  release_number
24
25
  }
25
26
  }
@@ -8,7 +8,7 @@ import DEFAULT_OPERATIONS from './collectibleGameSets.gql';
8
8
 
9
9
  export const useCollectibleGameSets = props => {
10
10
 
11
- const { product_type } = props
11
+ const { productType } = props
12
12
 
13
13
  const operations = mergeOperations(DEFAULT_OPERATIONS, null);
14
14
  const { getStoreConfigData, getCollectibleGameQuery } = operations;
@@ -28,11 +28,10 @@ export const useCollectibleGameSets = props => {
28
28
  const pathnameArr = pathname.split('/');
29
29
 
30
30
  const categoryUrlKey = pathnameArr[pathnameArr.length - 1].replace('.html','');
31
- const productType = product_type ? product_type : 'singles';
32
31
 
33
32
  const categoryUrlSuffix = storeConfigData?.storeConfig?.category_url_suffix;
34
33
 
35
- const { error, loading, data } = useQuery(getCollectibleGameQuery, {
34
+ const { error: allSetsError, loading: allSetsLoading, data: allSetsData } = useQuery(getCollectibleGameQuery, {
36
35
  fetchPolicy: 'cache-and-network',
37
36
  nextFetchPolicy: 'cache-first',
38
37
  skip: !storeConfigData,
@@ -42,26 +41,31 @@ export const useCollectibleGameSets = props => {
42
41
  }
43
42
  });
44
43
 
45
- const isBackgroundLoading = !!data && loading;
44
+ const { error: expansionSetsError, loading: expansionSetsLoading, data: expansionSetsData } = useQuery(getCollectibleGameQuery, {
45
+ fetchPolicy: 'cache-and-network',
46
+ nextFetchPolicy: 'cache-first',
47
+ skip: !storeConfigData,
48
+ variables: {
49
+ categoryUrlKey: categoryUrlKey,
50
+ productType: productType
51
+ }
52
+ });
53
+
54
+ const loading = !!allSetsData && allSetsLoading;
55
+ const error = !!allSetsData && allSetsError;
46
56
 
47
57
  const collectibleGameSets = useMemo(() => {
48
- if (!data) {
58
+ if (!allSetsData) {
49
59
  return null;
50
60
  }
51
61
 
52
- const collectibleGameSets = data.collectibleGameSets;
53
-
62
+ const collectibleGameSets = productType == "sealed-products" ? expansionSetsData.collectibleGameSets : allSetsData.collectibleGameSets;
54
63
  if (!collectibleGameSets) {
55
64
  return null;
56
65
  }
57
66
 
58
67
  return collectibleGameSets;
59
- }, [data, categoryUrlKey]);
60
-
61
- // Update the page indicator if the GraphQL query is in flight.
62
- useEffect(() => {
63
- setPageLoading(isBackgroundLoading);
64
- }, [isBackgroundLoading, setPageLoading]);
68
+ }, [allSetsData, expansionSetsData, categoryUrlKey, productType]);
65
69
 
66
70
  return {
67
71
  error,