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
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProductSuggestion.jsx 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { useState, useEffect } from 'react';
  2. import { Box, Typography, Button } from '@mui/material';
  3. import { useSelector } from 'react-redux';
  4. import Grid from '@mui/material/Grid2';
  5. import { useNavigate } from "react-router-dom";
  6. import defaultImage from "../../assets/images/default.png"
  7. import { getMenuCollectionTitlesByProductType } from '../../config/menuCollections';
  8. const normalizeTitle = (title = "") => title.trim().toUpperCase();
  9. const allowedSuggestionCollections = getMenuCollectionTitlesByProductType();
  10. // Function to check if a product is allowed in the suggestion based on its collections
  11. const isProductAllowedInSuggestion = (product) => {
  12. const allowedCollections = allowedSuggestionCollections[product?.productType] || [];
  13. const normalizedAllowedCollections = allowedCollections.map(normalizeTitle);
  14. return product?.collections?.some((collection) =>
  15. normalizedAllowedCollections.includes(normalizeTitle(collection?.title))
  16. );
  17. };
  18. const ProductSuggestion = () => {
  19. const [suggestProducts, setSuggestProducts] = useState([]);
  20. const products = useSelector((state) => state.products.products.data);
  21. const navigate = useNavigate();
  22. useEffect(() => {
  23. if (products.length > 0) {
  24. const getRandomProducts = (arr, num) => {
  25. const shuffled = [...arr].sort(() => 0.5 - Math.random());
  26. return shuffled.slice(0, num);
  27. };
  28. const visibleProducts = products.filter(isProductAllowedInSuggestion);
  29. // const randomProducts = getRandomProducts(products, 4);
  30. const randomProducts = getRandomProducts(visibleProducts, 4); // Select 4 random visible products
  31. setSuggestProducts(randomProducts);
  32. }
  33. }, [products]);
  34. const renderProduct = (
  35. handle,
  36. img_url,
  37. title,
  38. collection_name,
  39. minPrice,
  40. minPriceCurrency,
  41. maxPrice,
  42. maxPriceCurrency,
  43. minDiscountPrice,
  44. minDiscountPriceCurrency,
  45. maxDiscountPrice,
  46. maxDiscountPriceCurrency,
  47. extra_desc,
  48. selected = false
  49. ) => {
  50. return (
  51. <Grid
  52. className="animate__animated animate__fadeIn"
  53. item
  54. size={{ xs: 6, sm: 6, md: 3 }}
  55. >
  56. <a href={`/products/${handle}`} style={{ textDecoration: "none", color: "#000" }}>
  57. <Box
  58. sx={{
  59. overflow: "hidden",
  60. position: "relative",
  61. cursor: "pointer",
  62. }}
  63. onClick={() => {
  64. navigate(`/products/${handle}`)
  65. }}
  66. >
  67. <img
  68. src={img_url}
  69. alt={title}
  70. style={{
  71. width: "100%",
  72. aspectRatio: "3 / 4",
  73. objectFit: "cover",
  74. objectPosition: "top center",
  75. }}
  76. />
  77. {selected && (
  78. <Button
  79. sx={{
  80. position: "absolute",
  81. top: {
  82. xs: 0,
  83. sm: 0,
  84. md: 10,
  85. lg: 20,
  86. },
  87. left: {
  88. xs: 0,
  89. sm: 0,
  90. md: 10,
  91. lg: 20,
  92. },
  93. boxShadow: 0,
  94. fontSize: 10,
  95. }}
  96. variant="contained"
  97. >
  98. NEW
  99. </Button>
  100. )}
  101. <Box sx={{ pb: 3, pt: 1, width: "90%" }}>
  102. <Typography
  103. variant="body2"
  104. sx={{
  105. fontWeight: "100",
  106. fontSize: {
  107. xs: "0.65rem",
  108. sm: "0.65rem",
  109. md: "0.75rem",
  110. },
  111. }}
  112. >
  113. {collection_name}
  114. </Typography>
  115. <Typography
  116. variant="body2"
  117. sx={{
  118. fontWeight: "400",
  119. fontSize: {
  120. xs: "0.73rem",
  121. sm: "0.73rem",
  122. md: "0.875rem",
  123. },
  124. }}
  125. >
  126. {title}
  127. </Typography>
  128. <Typography
  129. variant="body2"
  130. sx={{
  131. fontWeight: "100",
  132. fontSize: {
  133. xs: "0.73rem",
  134. sm: "0.73rem",
  135. md: "0.875rem",
  136. },
  137. textDecoration: (minDiscountPrice > 0) ? "line-through" : "none"
  138. }}
  139. >
  140. {`${minPriceCurrency} ${parseFloat(minPrice).toFixed(2)}`}
  141. </Typography>
  142. {(minDiscountPrice > 0) && <Typography
  143. variant="body2"
  144. sx={{
  145. fontWeight: "100",
  146. fontSize: {
  147. xs: "0.73rem",
  148. sm: "0.73rem",
  149. md: "0.875rem",
  150. },
  151. }}
  152. >
  153. {`${minDiscountPriceCurrency} ${parseFloat(minDiscountPrice).toFixed(2)}`}
  154. </Typography>}
  155. </Box>
  156. </Box>
  157. </a>
  158. </Grid>
  159. );
  160. };
  161. return (
  162. <Box sx={{ mb: 5 }}>
  163. <Grid container spacing={1} columns={12}>
  164. {suggestProducts.map((product, index) => {
  165. let {
  166. handle,
  167. title,
  168. images,
  169. collections,
  170. minVariantPrice,
  171. maxVariantPrice,
  172. compareAtPriceRangeMinVariantPrice,
  173. compareAtPriceRangeMaxVariantPrice,
  174. // productType and variants are not needed here because filtering already happens before rendering.
  175. // productType,
  176. // variants,
  177. selected,
  178. } = product;
  179. let minPrice = minVariantPrice.amount;
  180. let minPriceCurrency = minVariantPrice.currencyCode;
  181. let maxPrice = maxVariantPrice.amount;
  182. let maxPriceCurrency = maxVariantPrice.currencyCode;
  183. // SHOULD BE THE DISCOUNTED PRICE
  184. let minDiscountPrice = compareAtPriceRangeMinVariantPrice.amount;
  185. let minDiscountPriceCurrency = compareAtPriceRangeMinVariantPrice.currencyCode;
  186. let maxDiscountPrice = compareAtPriceRangeMaxVariantPrice.amount;
  187. let maxDiscountPriceCurrency = compareAtPriceRangeMaxVariantPrice.currencyCode;
  188. let img_url = images[0]?.url || defaultImage
  189. let collection_name = collections[0]?.title;
  190. return renderProduct(
  191. handle,
  192. img_url,
  193. title,
  194. collection_name,
  195. minPrice,
  196. minPriceCurrency,
  197. maxPrice,
  198. maxPriceCurrency,
  199. minDiscountPrice,
  200. minDiscountPriceCurrency,
  201. maxDiscountPrice,
  202. maxDiscountPriceCurrency,
  203. "",
  204. selected
  205. );
  206. })}
  207. </Grid>
  208. </Box>
  209. );
  210. };
  211. export default ProductSuggestion;