Amber Shopify Project created using ReactJS+React-Redux with GraphQL API integration. Storefront Shopify API: https://github.com/Shopify/shopify-app-js/tree/main/packages/api-clients/storefront-api-client#readme

ProductList.jsx 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import { useEffect, useState } from 'react';
  2. import { Box, Typography, Button, FormControl, Select, MenuItem, InputBase } from '@mui/material';
  3. import Grid from '@mui/material/Grid2';
  4. import { styled } from "@mui/material";
  5. //REDUX
  6. import { useSelector, useDispatch } from 'react-redux';
  7. import { fetchProducts, fetchProductsByCollection } from '../../redux/slices/productSlice';
  8. //UTIL FUNCTION
  9. function getAllTags(data) {
  10. const products = data || [];
  11. const allTags = products.flatMap(product => product.tags);
  12. const uniqueTags = [...new Set(allTags)];
  13. return uniqueTags;
  14. }
  15. const BootstrapInput = styled(InputBase)(({ theme }) => ({
  16. 'label + &': {
  17. marginTop: theme.spacing(3),
  18. },
  19. '& .MuiInputBase-input': {
  20. position: 'relative',
  21. backgroundColor: "#2E2E2E",
  22. border: '1px solid #ced4da',
  23. color: "#FFF",
  24. fontSize: 13,
  25. padding: '5px 0',
  26. paddingRight: '50px !important',
  27. paddingLeft: "10px",
  28. transition: theme.transitions.create(['border-color', 'box-shadow']),
  29. '&:focus': {
  30. borderRadius: 4,
  31. borderColor: '#80bdff',
  32. boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
  33. },
  34. },
  35. '& .MuiSvgIcon-root': {
  36. color: "#FFF !important"
  37. },
  38. }));
  39. const ProductList = ({ size = 99999 }) => {
  40. const products = useSelector((state) => state.products.products.data) // only used as referenced
  41. const [filteredProducts, setFilteredProducts] = useState([]) // this one is the actual data to be rendered
  42. const [categoryFilterOption, setCategoryFilterOption] = useState([])
  43. const dispatch = useDispatch();
  44. //filter
  45. const [category, setCategory] = useState('all');
  46. const [sort, setSort] = useState('all')
  47. useEffect(() => {
  48. // if user come from select collection
  49. if (sessionStorage.getItem('amber-select-collection')) {
  50. let { id } = JSON.parse(sessionStorage.getItem('amber-select-collection'))
  51. dispatch(fetchProductsByCollection({ collectionId: id }))
  52. } else if (sessionStorage.getItem('amber-select-category')) {
  53. }
  54. }, [])
  55. useEffect(() => {
  56. let productType = sessionStorage.getItem('amber-select-product-type')
  57. let newFilteredProducts = products.filter(
  58. (product) => product.productType === productType
  59. );
  60. const categoryList = getAllTags(newFilteredProducts) // yes we use tags as category, while I was coding this, this is the only way to implement category
  61. setFilteredProducts(newFilteredProducts)
  62. setCategoryFilterOption(categoryList)
  63. }, [products])
  64. useEffect(() => {
  65. if (products?.length > 0) {
  66. let productType = sessionStorage.getItem('amber-select-product-type')
  67. let newFilteredProducts = products.filter(
  68. (product) => {
  69. if(category == 'all'){
  70. return product.productType === productType
  71. } else {
  72. return product.productType === productType && product.tags.includes(category)
  73. }
  74. }
  75. );
  76. setFilteredProducts(newFilteredProducts)
  77. }
  78. }, [category])
  79. useEffect(() => {
  80. if (products) console.log(products)
  81. }, [products])
  82. const handleChange = (event) => {
  83. //setInput({ ...input, [event.target.name]: event.target.value });
  84. };
  85. const renderProduct = (id, img_url, title, collection, price, currency, extra_desc) => {
  86. return (
  87. <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
  88. <Box
  89. onClick={() => { window.location.href = `/products/${id}` }}
  90. sx={{
  91. overflow: 'hidden',
  92. position: 'relative',
  93. cursor: 'pointer'
  94. }}
  95. >
  96. <img
  97. src={img_url}
  98. alt={title}
  99. style={{
  100. width: '100%',
  101. aspectRatio: '3 / 4',
  102. objectFit: 'cover',
  103. objectPosition: 'top center'
  104. }}
  105. />
  106. {/* <Button sx={{ position: "absolute", top: 20, left: 20, boxShadow: 0 }} variant="contained">
  107. NEW
  108. </Button> */}
  109. <Box sx={{ py: 5 }}>
  110. <Typography variant="body2" >
  111. {collection}
  112. </Typography>
  113. <Typography variant="h5" sx={{ fontWeight: "bolder" }}>
  114. {title}
  115. </Typography>
  116. <Typography variant="body" >
  117. {`${currency} ${parseFloat(price).toFixed(2)}`}
  118. </Typography>
  119. <Typography variant="body2" sx={{ mt: 2 }}>
  120. {extra_desc}
  121. </Typography>
  122. </Box>
  123. </Box>
  124. </Grid>
  125. )
  126. }
  127. return (
  128. <>
  129. {/* FILTER */}
  130. <Box
  131. sx={{
  132. display: "flex",
  133. justifyContent: "space-between",
  134. alignItems: "center",
  135. backgroundColor: "background.black",
  136. color: "white",
  137. px: 2, // Add padding around the box
  138. my: 4
  139. }}
  140. >
  141. {/* Left Side: Page Title */}
  142. <Typography variant="body2">
  143. {`${filteredProducts.length} Item`}
  144. </Typography>
  145. {/* Right Side: Option Inputs */}
  146. <Box sx={{ display: "flex", gap: 2 }}>
  147. <FormControl sx={{ m: 1, display: "flex", flexDirection: "row" }} variant="standard">
  148. <Typography variant="body2" sx={{ mr: 1, my: "auto" }}>Filter By Category</Typography>
  149. <Select
  150. value={category}
  151. onChange={(event) => {
  152. setCategory(event.target.value);
  153. }}
  154. input={<BootstrapInput />}
  155. name="type"
  156. >
  157. <MenuItem value={'all'}>All</MenuItem>
  158. {categoryFilterOption?.map((data) => (<MenuItem value={data}>{data}</MenuItem>))}
  159. </Select>
  160. </FormControl>
  161. <FormControl sx={{ m: 1, display: { xs: "none", sm: "none", md: "flex" }, flexDirection: "row" }} variant="standard">
  162. <Typography variant="body2" sx={{ mr: 1, my: "auto" }}>Sort By</Typography>
  163. <Select
  164. value={sort}
  165. onChange={handleChange}
  166. input={<BootstrapInput />}
  167. name="sort"
  168. >
  169. <MenuItem defaultValue value={''}>All</MenuItem>
  170. <MenuItem defaultValue value={'all'}>Newest</MenuItem>
  171. </Select>
  172. </FormControl>
  173. </Box>
  174. </Box>
  175. {/* LIST */}
  176. <Box sx={{ mb: 5 }}>
  177. <Grid container spacing={1} columns={12}>
  178. {filteredProducts.map((product, index) => {
  179. let { id, title, compareAtPriceRange, images, collections } = product
  180. let price = compareAtPriceRange.maxVariantPrice.amount
  181. let currency = compareAtPriceRange.maxVariantPrice.currencyCode
  182. let img_url = images.nodes[0].url
  183. let collection_name = collections.nodes[0]?.title
  184. // ID
  185. const parts = id.split('/');
  186. let prodID = parts[parts.length - 1];
  187. if (index < size) {
  188. return renderProduct(prodID, img_url, title, collection_name, price, currency, "")
  189. }
  190. })}
  191. </Grid>
  192. </Box>
  193. </>
  194. );
  195. };
  196. export default ProductList;