diff --git a/package.json b/package.json index 583c793..232e0dc 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "axios": "^0.19.2", "bootstrap": "^4.4.1", "classnames": "^2.2.6", - "node-sass": "^4.14.1", "prop-types": "^15.7.2", "react": "^16.13.1", "react-bootstrap": "^1.0.0-beta.17", @@ -33,10 +32,11 @@ "react-router-dom": "^5.1.2", "react-scripts": "3.4.0", "redux": "^4.0.5", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "sass": "^1.86.3" }, "scripts": { - "start": "react-scripts start", + "start": "set NODE_OPTIONS=--openssl-legacy-provider && react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", diff --git a/src/components/Products/FavouriteIcon.js b/src/components/Products/FavouriteIcon.js new file mode 100644 index 0000000..2e64ed0 --- /dev/null +++ b/src/components/Products/FavouriteIcon.js @@ -0,0 +1,16 @@ +import React, { useState } from 'react'; +import styles from './SingleProduct.module.css' + +const FavouriteIcon = ({isActive=false}) => { + const[hovered,setHovered]=useState(false) + const iconClass=hovered ||isActive ?'fas fa-heart' :'far fa-heart' + return ( +
setHovered(true)} + onMouseLeave={()=>setHovered(false)}> + +
+ ) +} + +export default FavouriteIcon \ No newline at end of file diff --git a/src/components/Products/NewArrivals.js b/src/components/Products/NewArrivals.js index 9c8fd6e..89e9ff9 100644 --- a/src/components/Products/NewArrivals.js +++ b/src/components/Products/NewArrivals.js @@ -4,105 +4,114 @@ ** Github URL: https://github.com/quintuslabs/fashion-cube */ -import React, { Component } from "react"; -import SingleProduct from "./SingleProduct"; -import Heading from "../Heading"; -import PropTypes from "prop-types"; -class NewArrivals extends Component { - constructor(props) { - super(props); - this.state = { - products: this.props.products, - productsBAK: this.props.products, - departments: this.props.departments - }; - } - - optionClicked(option) { - let FilterList = this.state.productsBAK.filter( - item => item.department === option - ); - if (FilterList.length > 0) { - this.setState({ products: FilterList }); - } else { - this.setState({ products: this.state.productsBAK }); - } - this.setState({ selectedOption: option }); - } - - render() { - const { products, departments } = this.state; - - return ( -
-
-
- -
-
-
-
-
    -
  • this.optionClicked("All")} - className={`grid_sorting_button button d-flex flex-column justify-content-center align-items-center ${ - this.state.selectedOption === "All" - ? "active is-checked" - : null - }`} - > - all -
  • -
  • this.optionClicked("Women")} - > - women's -
  • - -
  • this.optionClicked("Men")} - > - men's -
  • -
-
-
-
-
- {products && - products.slice(0, 8).map((item, index) => { - return ( -
- -
- ); - })} -
-
-
- ); - } -} - -NewArrivals.propTypes = { - addToCart: PropTypes.func -}; - -export default NewArrivals; + import React, { Component } from "react"; + import SingleProduct from "./SingleProduct"; + import Heading from "../Heading"; + import PropTypes from "prop-types"; + import jumpTo from "../../modules/Navigation"; + class NewArrivals extends Component { + constructor(props) { + super(props); + this.state = { + products: this.props.products, + productsBAK: this.props.products, + departments: this.props.departments + }; + } + + optionClicked(option) { + let FilterList = this.state.productsBAK.filter( + item => item.department === option + ); + if (FilterList.length > 0) { + this.setState({ products: FilterList }); + } else { + this.setState({ products: this.state.productsBAK }); + } + this.setState({ selectedOption: option }); + } + handleNavigate = (productId) => { + jumpTo(`/fashion-cube/single-product/${productId}`); + }; + handleAddToCart = (productId) => { + this.props.addToBag(productId) + }; + render() { + const { products, departments } = this.state; + + return ( +
+
+
+ + +
+
+
+
+
    +
  • this.optionClicked("All")} + className={`grid_sorting_button button d-flex flex-column justify-content-center align-items-center ${ + this.state.selectedOption === "All" + ? "active is-checked" + : null + }`} + > + all +
  • +
  • this.optionClicked("Women")} + > + women's +
  • + +
  • this.optionClicked("Men")} + > + men's +
  • +
+
+
+
+
+ {products && + products.slice(0, 8).map((item, index) => { + return ( +
+ +
+ ); + })} +
+
+
+ ); + } + } + + NewArrivals.propTypes = { + addToCart: PropTypes.func + }; + + export default NewArrivals; + \ No newline at end of file diff --git a/src/components/Products/ProductPrice.js b/src/components/Products/ProductPrice.js new file mode 100644 index 0000000..44a7f9b --- /dev/null +++ b/src/components/Products/ProductPrice.js @@ -0,0 +1,22 @@ +import React from 'react' +import styles from './SingleProduct.module.css'; +import SkeletonProduct from './SkeltonProduct'; +const ProductPrice = ({productItem}) => { + const newprice=productItem?.price + + if(!newprice){ + return( + + ) + } + const oldPrice=(parseFloat(newprice) + 30).toFixed(2) + return ( +

+ ₹ {oldPrice} + ₹ {newprice} + +

+ ) +} + +export default ProductPrice \ No newline at end of file diff --git a/src/components/Products/SingleProduct.js b/src/components/Products/SingleProduct.js index 5f932c4..db062d7 100644 --- a/src/components/Products/SingleProduct.js +++ b/src/components/Products/SingleProduct.js @@ -4,46 +4,67 @@ ** Github URL: https://github.com/quintuslabs/fashion-cube */ -import React from "react"; -import jumpTo from "../../modules/Navigation"; - -function SingleProduct(props) { - const { productItem } = props; - return ( -
-
- jumpTo(`/fashion-cube/single-product/${productItem._id}`) - } - > -
- -
-
- -
- {/*
- -$20 -
*/} -
-
-
{productItem.title}
-
-
- ₹ {productItem.price} - ₹ {(parseFloat(productItem.price) + 30).toFixed(2)} -
-
-
-
props.addToBag(productItem._id)} - > -
add to cart
-
-
- ); -} - -export default SingleProduct; + import React, { useState } from "react"; + import SkeletonProduct from "./SkeltonProduct"; + import FavouriteIcon from "./FavouriteIcon"; + import ProductPrice from "./ProductPrice"; + import styles from "./SingleProduct.module.css"; + const SingleProduct = ({ productItem, onNavigate, onAddToCart }) => { + const[imageError,setImageError]=useState(false) + if (!productItem) { + return ; + } + const HandleNavigate = () => { + onNavigate(productItem._id); + }; + const HandleAddToCart = (e) => { + e.stopPropagation(); + onAddToCart(productItem._id); + }; + return ( +
+
+
+ {!imageError ?{productItem.title}setImageError(true)} + />:} + +
+
+ +
+ {productItem.discount && ( +
+ -${productItem.discount} +
+ )} + +
+
+
{productItem.title}
+
+
+ +
+
+ +
+
+ ); + }; + + export default React.memo(SingleProduct); + \ No newline at end of file diff --git a/src/components/Products/SingleProduct.module.css b/src/components/Products/SingleProduct.module.css new file mode 100644 index 0000000..25ab66b --- /dev/null +++ b/src/components/Products/SingleProduct.module.css @@ -0,0 +1,209 @@ +.card { + height: 380px; + cursor: pointer; + margin-bottom: 30px; + position: relative; + border-radius: 5px; + overflow: hidden; + transition: box-shadow 0.3s ease; + + +} + +.card::after { + position: absolute; + top: 0; + left: -1px; + width: calc(100% + 1px); + height: 100%; + pointer-events: none; + content: ""; + border: 2px solid rgba(235, 235, 235, 0); + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + z-index: -1; +} + +.card:hover::after { + box-shadow: 0 25px 29px rgba(0, 0, 0, 0.2); + border-color: #f8e3e3; +} + +.product { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + background-color: #f2f2f261; +} + +.image { + width: 100%; + height: 240px; + display: flex; + justify-content: center; + overflow: hidden; +} + +.imageTag { + max-width: 100%; + max-height: 100%; + transition: transform 0.5s ease; + cursor: zoom-in; + object-fit: contain; +} + +.product:hover .imageTag { + transform: scale(1.2); +} + +.info { + text-align: center; + padding: 0 10px; + flex-grow: 1; + margin-bottom: 12px; +} + +.name { + margin-top: 20px; + font-size: 14px; + font-weight: 600; + color: #1e1e27; + line-height: 20px; +} + +.name:hover { + color: #da8692; +} + +.price { + position: relative; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + margin-top: 15px; + + +} + +.newprice { + font-size: 17px; + color: #fe4c50; + font-weight: 600; + animation-name: showNewPrice; +} + +.oldprice { + font-size: 14px; + margin-left: 10px; + color: #b5aec4; + text-decoration: line-through; + animation-name: showOldPrice; +} +.oldprice, +.newprice { + position: absolute; + animation-duration: 3s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + opacity: 0; +} + +@keyframes showOldPrice { + 0% { opacity: 0; transform: translateY(10px); } + 10% { opacity: 1; transform: translateY(0); } + 40% { opacity: 1; transform: translateY(0); } + 50% { opacity: 0; transform: translateY(-10px); } + 100% { opacity: 0; } +} + + +@keyframes showNewPrice { + 0%, 49% { opacity: 0; transform: translateY(10px); } + 60% { opacity: 1; transform: translateY(0); } + 90% { opacity: 1; } + 100% { opacity: 0; transform: translateY(-10px); } +} + +.productBubble { + position: absolute; + top: 12px; + right: 12px; + background: #fe4c50; + border-radius: 50px; + text-transform: uppercase; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + padding: 6px 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + font-weight: 600; + color: #fff; + animation: pulse 3s ease-in-out infinite; +} + +.productBubble span { + font-size: 12px; + font-weight: 600; + color: #ffffff; +} + +.addToCart { + width: 100%; + background-color: #fe4c50; + border-color: #fe4c50; + color: white; + padding: 8px 0; + font-size: 13px; + font-weight: 500; + border-radius: 5px; + text-align: center; + transition: background-color 0.3s ease; + cursor: pointer; + margin-top: auto; +} + +.addToCart:hover { + background-color: #fe7c7f; + border-color: #fe7c7f; +} + +.favorite { + position: absolute; + top: 20px; + left: 15px; + color: #b9b4c7; + width: 20px; + height: 20px; + z-index: 2; + transition: color 0.3s ease; + +} + +.favorite:hover { + color: #fe4c50; + animation: pulse 0.6s ease-in-out infinite; +} + +@keyframes pulse { + 0% { + opacity: 0.2; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0.2; + } +} + +.skeleton { + animation: pulse 1.9s ease-in-out infinite; + border-radius: 6px; + +} \ No newline at end of file diff --git a/src/components/Products/SkeltonProduct.js b/src/components/Products/SkeltonProduct.js new file mode 100644 index 0000000..6a182b0 --- /dev/null +++ b/src/components/Products/SkeltonProduct.js @@ -0,0 +1,18 @@ +import React from "react"; +import styles from './SingleProduct.module.css' +const SkeletonProduct = ({type}) => { + const base=styles.skeleton + if(type==='image')return
+ if(type==='text') return (
) + if(type==='product') + {return( +
+
+
+
+
+
+
) + +}}; +export default SkeletonProduct;