Simple shopping cart with React
A Pen by Ivan Markovic on CodePen.
Simple shopping cart with React
A Pen by Ivan Markovic on CodePen.
<div id="app"> | |
</div> |
let cats = [ | |
{ | |
id: "1", | |
name: "cat 1", | |
description: "Cat description goes here", | |
price: "12.99", | |
src: | |
"https://raw.githubusercontent.com/ivanmmarkovic/misc/master/images/cats-images-for-project/adorable-animal-blur-cat-617278.jpg", | |
count: "3" | |
}, | |
{ | |
id: "2", | |
name: "cat 2", | |
description: "Cat description goes here", | |
price: "22.99", | |
src: | |
"https://raw.githubusercontent.com/ivanmmarkovic/misc/master/images/cats-images-for-project/animal-cat-face-close-up-feline-416160.jpg", | |
count: "2" | |
}, | |
{ | |
id: "3", | |
name: "cat 3", | |
description: "Cat description goes here", | |
price: "17.99", | |
src: | |
"https://raw.githubusercontent.com/ivanmmarkovic/misc/master/images/cats-images-for-project/adorable-animal-cat-302280.jpg", | |
count: "5" | |
}, | |
{ | |
id: "4", | |
name: "cat 4", | |
description: "Cat description goes here", | |
price: "27.99", | |
src: | |
"https://raw.githubusercontent.com/ivanmmarkovic/misc/master/images/cats-images-for-project/adorable-animal-baby-blur-177809.jpg", | |
count: "3" | |
} | |
]; | |
let comments = ["Do you have a cat with a hat?", "Nice"]; | |
class App extends React.Component { | |
constructor(props) { | |
super(); | |
this.state = { | |
cats: cats, | |
cart: [], | |
display: { | |
cats: true, | |
cart: false, | |
comments: false | |
}, | |
comments: comments, | |
cartLinkClassName: "material-icons" | |
} | |
} | |
addToCart(id) { | |
console.log("Added :", id); | |
let cats = this.state.cats; | |
let cart = this.state.cart; | |
for (let i = 0; i < cats.length; i++) { | |
if (cats[i].id == id && cats[i].count > 0) { | |
if (cart.length === 0) { | |
cats[i].count = cats[i].count - 1; | |
let cat = Object.assign({}, cats[i]); | |
delete(cat.count); | |
cat.howMany = typeof cat.howMany === "undefined" ? 1 : cat.howMany + 1; | |
cart.push(cat); | |
} else { | |
let result = cart.filter((item) => item.id == cats[i].id); | |
if (result.length === 1) { | |
for (let j = 0; j < cart.length; j++) { | |
if (cart[j].id == cats[i].id) { | |
cats[i].count = cats[i].count - 1; | |
cart[j].howMany++; | |
} | |
} | |
} else { | |
cats[i].count--; | |
let cat = Object.assign({}, cats[i]); | |
delete(cat.count); | |
cat.howMany = typeof cat.howMany === "undefined" ? 1 : cat.howMany + 1; | |
cart.push(cat); | |
} | |
} | |
} | |
} | |
this.setState({ | |
cats: cats, | |
cart: cart, | |
cartLinkClassName: "material-icons rotateBasket" | |
}); | |
setTimeout(() => this.setState({ | |
cartLinkClassName: "material-icons" | |
}), 2000); | |
} | |
removeItem(id) { | |
let cats = this.state.cats; | |
let cart = this.state.cart; | |
let itemsToGetBack = 0; | |
for (let i = 0; i < cart.length; i++) { | |
if (cart[i].id == id) { | |
let cartIndex = i; | |
itemsToGetBack = cart[i].howMany; | |
cart.splice(cartIndex, 1); | |
} | |
} | |
for (let i = 0; i < cats.length; i++) { | |
if (cats[i].id == id) { | |
cats[i].count = cats[i].count + itemsToGetBack; | |
} | |
} | |
this.setState({ | |
cats: cats, | |
cart: cart | |
}) | |
} | |
displayThisComponent(comp) { | |
switch (comp) { | |
case "cats": | |
this.setState({ | |
display: { | |
cats: true, | |
cart: false, | |
comments: false | |
} | |
}); | |
break; | |
case "cart": | |
this.setState({ | |
display: { | |
cats: false, | |
cart: true, | |
comments: false | |
} | |
}); | |
break; | |
case "comments": | |
this.setState({ | |
display: { | |
cats: false, | |
cart: false, | |
comments: true | |
} | |
}); | |
break; | |
} | |
} | |
addComment(comment) { | |
let comments = this.state.comments; | |
comments.push(comment); | |
this.setState({ | |
comments: comments | |
}) | |
} | |
render() { | |
let cats = this.state.display.cats ? <Cats cats={this.state.cats} addToCart={this.addToCart.bind(this)} /> : ""; | |
let cart = this.state.display.cart ? <Cart cart={this.state.cart} removeItem={this.removeItem.bind(this)}/> : ""; | |
let comments = this.state.display.comments ? <Comments comments={this.state.comments} addComment={this.addComment.bind(this)}/> : ""; | |
return ( | |
<div className="container"> | |
<nav> | |
<span onClick={() => this.displayThisComponent("cats")}> | |
<i className="material-icons">store</i> | |
</span> | |
<span onClick={() => this.displayThisComponent("cart")}> | |
<i className={this.state.cartLinkClassName}>add_shopping_cart</i> | |
</span> | |
<span onClick={() => this.displayThisComponent("comments")}> | |
<i className="material-icons">chat_bubble</i> | |
</span> | |
</nav> | |
{cats} | |
{cart} | |
{comments} | |
</div> | |
); | |
} | |
} | |
class Cats extends React.Component { | |
render() { | |
var addToCart = this.props.addToCart; | |
var catsNodes = this.props.cats.map((cat, i) => <Cat key={i} cat={cat} addToCart={addToCart} />); | |
return ( | |
<div className="cats-all" > | |
{catsNodes} | |
</div> | |
); | |
} | |
}; | |
class Cat extends React.Component { | |
render() { | |
return ( | |
<div className="single-cat"> | |
<h3>{this.props.cat.name}</h3> | |
<img src={this.props.cat.src} /> | |
<p><b>Price</b> {this.props.cat.price} | {this.props.cat.count} {" "}available</p> | |
<span onClick={() => this.props.addToCart(this.props.cat.id)}><i className="material-icons">add_shopping_cart</i></span> | |
</div> | |
) | |
} | |
}; | |
class Cart extends React.Component { | |
removeItem(id) { | |
this.props.removeItem(id); | |
} | |
render() { | |
const removeItem = this.removeItem.bind(this); | |
let totalPrice = 0; | |
for (let i = 0; i < this.props.cart.length; i++) { | |
totalPrice = totalPrice + (this.props.cart[i].price * this.props.cart[i].howMany); | |
} | |
totalPrice = totalPrice.toFixed(2); | |
let itemsNodes = this.props.cart.map((item, i) => { | |
let singleCatTotal = (item.price * item.howMany).toFixed(2); | |
return ( | |
<div key={i} className="single-cat"> | |
<h3>{item.name}</h3> | |
<img src={item.src} /> | |
<p>{item.description}</p> | |
<p>Items {item.howMany} | price {item.price} | total {singleCatTotal}</p> | |
<span onClick={() => removeItem(item.id)}><i className="material-icons">delete</i></span> | |
</div> | |
) | |
}); | |
return ( | |
<div className="cart"> | |
<div className="items-wraper"> | |
{itemsNodes} | |
</div> | |
<h3>Total : {totalPrice}</h3> | |
</div> | |
); | |
} | |
}; | |
class Comments extends React.Component { | |
constructor(props) { | |
super(); | |
this.state = { | |
comment: "" | |
} | |
} | |
getComment(event) { | |
this.setState({ | |
comment: event.target.value | |
}); | |
} | |
addComment() { | |
if (this.state.comment != "") { | |
this.props.addComment(this.state.comment); | |
} | |
this.setState({ | |
comment: "" | |
}) | |
} | |
render() { | |
let commentsNodes = this.props.comments.map((comment, i) => <div key={i}><p>{comment}</p></div>); | |
return ( | |
<div className="comments"> | |
<h1>Comments</h1> | |
<input type="text" onChange={this.getComment.bind(this)} value={this.state.comment}/> | |
<span><i className="material-icons" onClick={this.addComment.bind(this)}>message</i></span> | |
<div className="comments-wrapper"> | |
{commentsNodes} | |
</div> | |
</div> | |
) | |
} | |
}; | |
ReactDOM.render( | |
<App cats={cats}/>, | |
document.getElementById("app") | |
); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js"></script> |
* { | |
box-sizing: border-box; | |
margin: 0; | |
padding: 0; | |
} | |
h1, p { | |
font-family: Lato; | |
} | |
.container { | |
position: relative; | |
width: 100%; | |
overflow: hidden; | |
} | |
nav { | |
position: fixed; | |
width: 100%; | |
height: 40px; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
background-color: crimson; | |
} | |
nav > span { | |
cursor: pointer; | |
color: #fff; | |
margin: 0 15px; | |
} | |
.rotateBasket { | |
animation-name: rotateBasket; | |
animation-duration: 2s; | |
animation-delay: 0s; | |
animation-direction: normal; | |
animation-iteration-count: 2; | |
animation-fill-mode: forwards; | |
animation-timing-function: linear; | |
} | |
@keyframes rotateBasket { | |
0% { | |
transform: rotate(0deg); | |
} | |
20% { | |
transform: rotate(45deg); | |
} | |
40% { | |
transform: rotate(-45deg); | |
} | |
60% { | |
transform: rotate(45deg); | |
} | |
80% { | |
transform: rotate(-45deg); | |
} | |
100% { | |
transform: rotate(0deg); | |
} | |
} | |
.comments { | |
margin: 40px auto; | |
display: flex; | |
flex-direction: column; | |
justify-content: flex-start; | |
max-width: 1200px; | |
} | |
.comments > h1 { | |
align-self: center; | |
color: crimson; | |
margin: 20px; | |
} | |
.comments > input { | |
align-self: center; | |
border: 1px solid lightgray; | |
padding: 10px; | |
box-shadow: 1px 3px 5px -7px rgb(221, 208, 208); | |
width: 400px; | |
margin-bottom: 20px; | |
} | |
.comments > span { | |
align-self: center; | |
color: crimson; | |
margin-bottom: 15px; | |
cursor: pointer; | |
} | |
.comments-wrapper { | |
align-self: center; | |
width: 400px; | |
margin: 10px auto; | |
display: flex; | |
flex-direction: column; | |
} | |
.comments-wrapper p { | |
display: inline-flex; | |
width: 100%; | |
flex-direction: row; | |
justify-content: flex-start; | |
align-items: center; | |
color: dimgray; | |
margin: auto 5px; | |
padding: 20px; | |
border-radius: 7px; | |
border: 1px solid rgba(214, 192, 192, 0.808); | |
} | |
@media all and (max-width: 450px){ | |
.input, .comments-wrapper { | |
width: 100%; | |
margin: 0 10px; | |
} | |
.comments-wrapper p { | |
margin: 0; | |
} | |
} | |
.cats-all { | |
margin: 40px auto; | |
overflow: hidden; | |
display: flex; | |
justify-content: flex-start; | |
align-items: flex-start; | |
flex-wrap: wrap; | |
max-width: 1200px; | |
} | |
.single-cat { | |
overflow: hidden; | |
height: 550px; | |
width: 30.333%; | |
margin: 10px 1.5%; | |
display: inline-flex; | |
flex-direction: column; | |
border: 1px solid rgba(241, 235, 235, 0.452); | |
border-radius: 7px; | |
box-shadow: 1px 3px 5px -7px #000; | |
} | |
.single-cat > h3 { | |
align-self: flex-start; | |
color: crimson; | |
font-size: 1.4em; | |
margin: 20px; | |
} | |
.single-cat > img { | |
margin: 0 auto; | |
height: 340px; | |
} | |
.single-cat > p{ | |
padding: 20px; | |
} | |
.single-cat > span { | |
padding: 20px; | |
color: crimson; | |
font-size: 1.2em; | |
width: 30px; | |
height: 30px; | |
align-self: flex-start; | |
margin: 20px; | |
display: inline-flex; | |
border-radius: 50%; | |
justify-content: center; | |
align-items: center; | |
background-color: rgba(211, 205, 205, 0.651); | |
} | |
.single-cat > span > i { | |
cursor: pointer; | |
} | |
@media all and (max-width: 960px){ | |
.single-cat { | |
width: 45%; | |
margin: 10px 2.5%; | |
} | |
} | |
@media all and (max-width: 680px){ | |
.single-cat { | |
width: 100%; | |
} | |
} | |
.cart { | |
margin: 40px auto; | |
max-width: 1200px; | |
} | |
.items-wrapper { | |
overflow: hidden; | |
display: flex; | |
justify-content: flex-start; | |
align-items: flex-start; | |
flex-wrap: wrap; | |
} | |
.items-wrapper > .single-cat { | |
overflow: hidden; | |
height: 550px; | |
width: 30.333%; | |
margin: 10px 1.5%; | |
display: inline-flex; | |
flex-direction: column; | |
border: 1px solid rgba(241, 235, 235, 0.452); | |
border-radius: 7px; | |
box-shadow: 1px 3px 5px -7px #000; | |
} | |
.items-wrapper > .single-cat > h3 { | |
align-self: flex-start; | |
color: crimson; | |
font-size: 1.4em; | |
margin: 20px; | |
} | |
.items-wrapper > .single-cat > img { | |
margin: 0 auto; | |
height: 340px; | |
} | |
.single-cat > p{ | |
padding: 20px; | |
} | |
.single-cat > span { | |
padding: 20px; | |
color: crimson; | |
font-size: 1.2em; | |
width: 30px; | |
height: 30px; | |
align-self: flex-start; | |
margin: 20px; | |
display: inline-flex; | |
border-radius: 50%; | |
justify-content: center; | |
align-items: center; | |
background-color: rgba(211, 205, 205, 0.651); | |
} | |
.items-wrapper > .single-cat > span > i { | |
cursor: pointer; | |
} | |
.cart > h3 { | |
margin: 50px 20px; | |
} | |
@media all and (max-width: 960px){ | |
.single-cat { | |
width: 45%; | |
margin: 10px 2.5%; | |
} | |
} | |
@media all and (max-width: 680px){ | |
.single-cat { | |
width: 100%; | |
margin: 0; | |
} | |
} |