shopping cart has price tags, default price is $10.
login as bob, change stages price to $8
stages is now $8
Nick's cart, stages price changed, total changed.
change stages qty to 7, total changed.
delete stages, total updated.
#django/modals
class Album(models.Model):
artist = models.CharField(max_length=50)
album_title = models.CharField(max_length=50)
genre = models.CharField(max_length=50)
album_logo = models.FileField()
date_posted = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
price = models.DecimalField(decimal_places=2,max_digits=5, default=10)
-------------------------------
#django/api/serializers
class MusicSerializer(serializers.ModelSerializer):
class Meta:
model = Album
fields = ('id', 'artist', 'album_title', 'genre', 'album_logo', 'date_posted', 'author', 'price')
--------------------------------
//react/pages/shoppingCart
import React, { Component } from 'react';
import '../App.css';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { getShoppingItems } from '../redux/actions/getShoppingIItems';
import { getAlbums } from '../redux/actions/getAlbums';
import { getUsers } from '../redux/actions/getUser';
import { putShoppingItem } from '../redux/actions/putShoppingItem';
import { deleteShoppingItem } from '../redux/actions/deleteShoppingItem'
import { Input, message, Tag } from 'antd';
import { Button } from 'reactstrap';
import { MdExposureNeg1, MdExposurePlus1 } from "react-icons/md";
class ShoppingCart extends Component {
constructor(props) {
super(props);
this.state = {
qty: [],
qty_input: [],
qty_loaded: false,
};
}
componentDidMount() {
//start fetching database once logged in
if (this.props.loggedin) {
if (!this.props.gotAlbums) {
this.props.dispatch(getAlbums(this.props.token));
}
if (!this.props.gotUsers) {
this.props.dispatch(getUsers(this.props.token));
}
if (!this.props.gotShoppingItems) {
this.props.dispatch(getShoppingItems(this.props.token));
}
//wait for 5sec, check every 0.1s to see if shoppingItems are fetched
let i = 0;
const waitShoppingItem = setInterval(async () => {
if (this.props.gotShoppingItems) {
await this.setState({ qty: [], qty_input: [], qty_loaded: false })
this.props.shoppingItems.map(async (item, index) => {
await this.setState(prevState => {
return {
qty: prevState.qty.concat({ album_id: item.album, qty: item.quantity }),
qty_input: prevState.qty_input.concat({ album_id: item.album, qty: null })
}
})
if (this.state.qty.length === this.props.shoppingItems.length) { this.setState({ qty_loaded: true }) }
})
clearInterval(waitShoppingItem);
}
if (i == 50) {
message.error('fetching shopping items timed out.')
clearInterval(waitShoppingItem);
}
i++;
}, 100)
}
}
add = async (album_id) => {
await this.setState(prevState => { return { qty_input: prevState.qty_input.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: null }) } });
this.setState(prevState => { return { qty: prevState.qty.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: prevState.qty.find(item => item.album_id === album_id).qty + 1 }) } })
}
subtract = async (album_id) => {
await this.setState(prevState => { return { qty_input: prevState.qty_input.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: null }) } });
if (this.state.qty.find(item => item.album_id === album_id).qty > 1) {
this.setState(prevState => { return { qty: prevState.qty.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: prevState.qty.find(item => item.album_id === album_id).qty - 1 }) } })
}
}
inputChange = (e, album_id) => {
if (e.target.value > 0) {
const newQty = parseInt(e.target.value)
this.setState(prevState => { return { qty: prevState.qty.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: newQty }) } })
this.setState(prevState => { return { qty_input: prevState.qty_input.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: newQty }) } })
} else {
message.error('entered invalid value')
}
}
deleteItem = (id) => {
this.props.dispatch(deleteShoppingItem(this.props.token, id));
//wait for 5sec, check every sec to see if delete successful
let i = 0;
const waitDelete = setInterval(() => {
if (this.props.shoppingItemDeleted) {
clearInterval(waitDelete);
}
if (i == 50) {
message.error('connection timed out.')
clearInterval(waitDelete);
}
i++;
}, 100)
}
updateCart = () => {
this.state.qty.map((item, index) => {
//only update items with qty changed
const oldItem = this.props.shoppingItems.find(i => i.album === item.album_id)
if (item.qty !== oldItem.quantity) {
const formData = new FormData();
formData.set('shopper', this.props.users.find(user => user.username === this.props.username).id);
formData.set('album', item.album_id);
formData.set('quantity', item.qty);
this.props.dispatch(putShoppingItem(this.props.token, oldItem.id, formData));
//wait for 5sec, check every sec to see if updateShoppingItem successful
let i = 0;
const waitUpdate = setInterval(() => {
const newItem = this.props.shoppingItems.find(i => i.album === item.album_id)
if (newItem.quantity === item.qty) {
message.success('Updated quantity of ' + this.props.albums.filter(album => album.id === item.album_id)[0].album_title + ' to ' + item.qty)
clearInterval(waitUpdate);
}
if (i == 50) {
message.error('connection timed out.')
clearInterval(waitUpdate);
}
i++;
}, 100)
}
})
}
render() {
if (!this.props.loggedin) {
return <Redirect to='/login' />
}
let total = 0
return (
<div style={{ padding: '10px', marginTop: '10px' }}>
<legend>Shopping Cart</legend>
<hr />
<div style={{ color: 'red' }}>{this.props.errorShoppingItem} {this.props.errorAlbum}</div>
{
this.state.qty_loaded ?
this.props.shoppingItems
.sort((a, b) => { return this.props.albums.find(album => album.id === a.album).album_title.toUpperCase().localeCompare(this.props.albums.find(album => album.id === b.album).album_title.toUpperCase()) })
.map((item, index) => {
const album = this.props.albums.find(album => album.id === item.album)
total = total + album.price * item.quantity
return <div key={index}>
<div style={{ width: '90%', display: 'flex', justifyContent: 'space-between' }}>
<div style={{ width: '90%', display: 'flex', justifyContent: 'space-between' }}>
<span style={{ fontStyle: 'italic' }}>{album.album_title} <Tag color='geekblue'>${album.price}</Tag></span>
<Input
type='number'
onChange={(e) => { this.inputChange(e, item.album) }}
style={{ width: '150px' }}
addonBefore={<MdExposureNeg1 onClick={() => this.subtract(item.album)} style={{ cursor: 'pointer', fontSize: '20px' }} />}
addonAfter={<MdExposurePlus1 onClick={() => this.add(item.album)} style={{ cursor: 'pointer', fontSize: '20px' }} />}
value={this.state.qty_input.find(i => i.album_id === item.album).qty || this.state.qty.find(i => i.album_id === item.album).qty}
/>
</div>
<b style={{ textAlign: 'right' }} onClick={() => this.deleteItem(item.id)} style={{ cursor: 'pointer' }}>X</b>
</div>
<hr />
</div>
})
: null
}
<div style={{ width: '80%', display: 'flex', justifyContent: 'space-between' }}>
<Button color='success' size='sm' onClick={() => this.updateCart()}>Update</Button>
<span>Total: <Tag color='geekblue'>${total}</Tag></span>
</div>
</div>
);
}
}
export default connect(
(store) => {
return {
loggedin: store.login.fetched,
gotShoppingItems: store.shoppingItems.fetched,
shoppingItems: store.shoppingItems.shoppingItems,
errorShoppingItem: store.shoppingItems.error,
shoppingItemUpdated: store.shoppingItems.updated,
shoppingItemDeleted: store.shoppingItems.deleted,
albums: store.albums.albums,
gotAlbums: store.albums.fetched,
errorAlbum: store.albums.error,
users: store.users.users,
gotUsers: store.users.fetched,
errorUser: store.users.error,
username: store.login.username,
token: store.login.token,
loggedin: store.login.fetched,
};
}
)(ShoppingCart);
No comments:
Post a Comment