Commit fe877568 authored by Shaphen Pangburn's avatar Shaphen Pangburn

Merge branch 'AFP139-OrderPage-Cart-Promotions' into 'Dev'

Afp139 order page cart promotions

See merge request !31
parents 1e9f2de0 257b43e9
...@@ -41,7 +41,7 @@ public class ProductService { ...@@ -41,7 +41,7 @@ public class ProductService {
public Flux<Promotion> getAllPromotions() { public Flux<Promotion> getAllPromotions() {
return WebClient return WebClient
.builder() .builder()
.baseUrl(promoManagementUrl) .baseUrl("http://localhost:8082")
.build() .build()
.get() .get()
.uri("/api/promos") .uri("/api/promos")
......
...@@ -7,6 +7,10 @@ spring.data.mongodb.uri=mongodb+srv://ecom:ecom@e-commerce-db-cluster.va815.mong ...@@ -7,6 +7,10 @@ spring.data.mongodb.uri=mongodb+srv://ecom:ecom@e-commerce-db-cluster.va815.mong
spring.data.mongodb.database=e-commerce-db spring.data.mongodb.database=e-commerce-db
security.enable-csrf=false security.enable-csrf=false
server.port=8080 server.port=8080
#
#products.apiUrl=http://13.64.175.185:8080
#promos.apiUrl=http://40.118.215.99:8082
#orders.apiUrl=http://138.91.251.222:8086
# UNCOMMENT FOR PRODUCTION DEPLOYMENT # UNCOMMENT FOR PRODUCTION DEPLOYMENT
#products.apiUrl=http://13.64.175.185:8080 #products.apiUrl=http://13.64.175.185:8080
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import {priceCalcUtil, numItemsCalcUtil} from '../util/cart_processing_util' import {priceCalcUtil, numItemsCalcUtil, promoCalcUtil} from '../util/cart_processing_util'
export const GET_TOTAL_PRICE = "GET_TOTAL_PRICE" export const GET_TOTAL_PRICE = "GET_TOTAL_PRICE"
export const GET_NUM_ITEMS = "GET_NUM_ITEMS" export const GET_NUM_ITEMS = "GET_NUM_ITEMS"
export const GET_CART_PRODUCTS = "GET_CART_PRODUCTS" export const GET_CART_PRODUCTS = "GET_CART_PRODUCTS"
export const GET_TOTAL_PROMOTIONS = "GET_TOTAL_PROMOTIONS"
export const getTotalPrice = (totalPrice) => ({ export const getTotalPrice = (totalPrice) => ({
type: GET_TOTAL_PRICE, type: GET_TOTAL_PRICE,
...@@ -19,6 +20,11 @@ export const getCartProducts = (cartProducts) => ({ ...@@ -19,6 +20,11 @@ export const getCartProducts = (cartProducts) => ({
payload: cartProducts payload: cartProducts
}) })
export const getTotalPromotions = (totalPromotions) => ({
type: GET_TOTAL_PROMOTIONS,
payload: totalPromotions
})
export const calcTotalPrice = (productList) => { export const calcTotalPrice = (productList) => {
return (dispatch) => { return (dispatch) => {
...@@ -26,6 +32,12 @@ export const calcTotalPrice = (productList) => { ...@@ -26,6 +32,12 @@ export const calcTotalPrice = (productList) => {
} }
} }
export const calcTotalPromotions = (productList) => {
return (dispatch) => {
return dispatch(getTotalPromotions(promoCalcUtil(productList)))
}
}
export const calcNumItems = (productList) => { export const calcNumItems = (productList) => {
return (dispatch) => { return (dispatch) => {
return dispatch(getNumItems(numItemsCalcUtil(productList))) return dispatch(getNumItems(numItemsCalcUtil(productList)))
......
...@@ -9,10 +9,12 @@ const receiveAllProducts = products => ({ ...@@ -9,10 +9,12 @@ const receiveAllProducts = products => ({
products products
}) })
const receiveAllPromotions = promotions => ({ const receiveAllPromotions = promotions => {
type: RECEIVE_ALL_PROMOTIONS, return {
promotions type: RECEIVE_ALL_PROMOTIONS,
}) promotions
}
}
const receiveAllProductsAndPromotions = prodsAndPromos => ({ const receiveAllProductsAndPromotions = prodsAndPromos => ({
type: RECEIVE_ALL_PRODUCTS_AND_PROMOTIONS, type: RECEIVE_ALL_PRODUCTS_AND_PROMOTIONS,
......
...@@ -121,4 +121,4 @@ ...@@ -121,4 +121,4 @@
.company-banner { .company-banner {
height: 50; height: 50;
width: 100px; width: 100px;
} }
\ No newline at end of file
...@@ -22,58 +22,81 @@ export default function ReviewOrder(props) { ...@@ -22,58 +22,81 @@ export default function ReviewOrder(props) {
<p>Step 3 - Order Summary</p> <p>Step 3 - Order Summary</p>
</div> </div>
<div id="orderSummaryContainer">
<div className="OrderSummaryElement" id="itemsPrice">
<div>
Items ({props.numCartItems}):
</div>
<div> {
${parseFloat(props.itemsTotal).toFixed(2)} props.loading === 0 ?
<div id="orderSummaryContainer">
<div className="OrderSummaryElement" id="itemsPrice">
<div>
Items ({props.numCartItems}):
</div>
<div>
${parseFloat(props.itemsTotal).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="promotionsApplied">
<div>
Promotions Discount:
</div>
<div>
<span style={{color: 'green', fontWeight: 'bold'}}>
-${parseFloat(props.promotionsDiscount).toFixed(2)}
</span>
</div>
</div>
<div className="OrderSummaryElement" id="shippingHandling">
<div id="shippingHandlingLabel">
Shipping and Handling:
</div>
<div id="shippingHandlingPrice">
{/* ${parseFloat(props.shippingHandling).toFixed(2)} */}
<span style={{color: 'green', fontWeight: 'bold'}}>FREE</span>
</div>
</div>
<div className="OrderSummaryElement" id="beforeTax">
<div id="beforeTaxLabel">
Total Before Tax
</div>
<div id="beforeTaxPrice">
${calcTotalBeforeTax(props.itemsTotal - props.promotionsDiscount, props.shippingHandling).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="taxCalculated">
<div id="taxCalculatedLabel">
Estimated Tax:
</div>
<div id="taxCalculatedPrice">
${calcTaxAmount(props.itemsTotal - props.promotionsDiscount, props.shippingHandling, props.taxRate).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="orderTotal">
<div id="orderTotalLabel">
Order Total:
</div>
<div id="orderTotalPrice">
${calcGrandTotal(props.itemsTotal - props.promotionsDiscount, props.shippingHandling, props.taxRate).toFixed(2)}
</div>
</div>
</div> </div>
</div> :
<div className="OrderSummaryElement" id="shippingHandling"> <div id="loadingElement">
<div id="shippingHandlingLabel"> <div class="lds-ring"><div></div><div></div><div></div><div></div></div>
Shipping and Handling:
</div> </div>
}
<div id="shippingHandlingPrice">
${parseFloat(props.shippingHandling).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="beforeTax">
<div id="beforeTaxLabel">
Total Before Tax
</div>
<div id="beforeTaxPrice">
${calcTotalBeforeTax(props.itemsTotal, props.shippingHandling).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="taxCalculated">
<div id="taxCalculatedLabel">
Estimated Tax:
</div>
<div id="taxCalculatedPrice">
${calcTaxAmount(props.itemsTotal, props.shippingHandling, props.taxRate).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="orderTotal">
<div id="orderTotalLabel">
Order Total:
</div>
<div id="orderTotalPrice">
${calcGrandTotal(props.itemsTotal, props.shippingHandling, props.taxRate).toFixed(2)}
</div>
</div>
</div>
</div> </div>
) )
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
padding-top: 40px;
} }
.OrderDirectionsElement { .OrderDirectionsElement {
...@@ -47,8 +48,9 @@ ...@@ -47,8 +48,9 @@
flex-direction: row; flex-direction: row;
width: 40%; width: 40%;
justify-content: space-between; justify-content: space-between;
margin-bottom: 10px; margin-bottom: 15px;
font-size: 150%; font-size: 150%;
} }
#orderTotal { #orderTotal {
...@@ -79,4 +81,52 @@ ...@@ -79,4 +81,52 @@
color: red; color: red;
font-weight: bold; font-weight: bold;
font-size: 120%; font-size: 120%;
} }
\ No newline at end of file
/* ////// */
.lds-ring {
display: inline-block;
position: relative;
width: 150px;
height: 150px;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 150px;
height: 150px;
margin: 8px;
border: 8px solid #000;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: rgb(0, 77, 112) transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#loadingElement {
display: flex;
justify-content: center;
align-items: center;
}
...@@ -9,7 +9,7 @@ import './checkout.css' ...@@ -9,7 +9,7 @@ import './checkout.css'
import {dispatchOrderInfo} from './../../actions/checkout_actions' import {dispatchOrderInfo} from './../../actions/checkout_actions'
import { Redirect } from 'react-router' import { Redirect } from 'react-router'
import { updateUserCart } from './../../actions/cart_actions' import { updateUserCart } from './../../actions/cart_actions'
import { calcTotalPrice, calcNumItems, storeCartItemsInProcessing } from './../../actions/cart_processing_actions' import { calcTotalPrice, calcNumItems, storeCartItemsInProcessing, calcTotalPromotions } from './../../actions/cart_processing_actions'
export default function Checkout() { export default function Checkout() {
const dispatch = useDispatch() const dispatch = useDispatch()
/////////////////////// ///////////////////////
...@@ -32,17 +32,18 @@ export default function Checkout() { ...@@ -32,17 +32,18 @@ export default function Checkout() {
/////////////////////// ///////////////////////
// Billing Info State // Billing Info State
/////////////////////// ///////////////////////
const [cardNumber, setCardNumber] = useState(["123456789"]) const [cardNumber, setCardNumber] = useState(["123456789123456"])
const [cardHolderName, setCardHolderName] = useState(["Guy Fieri"]) const [cardHolderName, setCardHolderName] = useState(["Guy Fieri"])
const [expirationDate, setExpirationDate] = useState(["05/20206"]) const [expirationDate, setExpirationDate] = useState(["05/2026"])
const [cvv, setCVV] = useState(["123"]) const [cvv, setCVV] = useState(["123"])
/////////////////////// ///////////////////////
// Order Review / Summary State // Order Review / Summary State
/////////////////////// ///////////////////////
const numCartItems = useSelector(state => state.cartProcessing.numItems) const numCartItems = useSelector(state => state.cartProcessing.numItems)
const itemsTotal = useSelector(state => state.cartProcessing.totalPrice) const itemsTotal = useSelector(state => state.cartProcessing.totalPrice)
const taxRate = 0.0725 const promotionsDiscount = useSelector(state => state.cartProcessing.totalPromotions)
const shippingHandling = 9.00 const taxRate = 0.0
const shippingHandling = 0.00
/////////////////////// ///////////////////////
// Submit Button State // Submit Button State
/////////////////////// ///////////////////////
...@@ -54,6 +55,7 @@ export default function Checkout() { ...@@ -54,6 +55,7 @@ export default function Checkout() {
/////////////////////// ///////////////////////
const user = useSelector(state => state.user.currentUser) const user = useSelector(state => state.user.currentUser)
const cart = useSelector(state => state.cart) const cart = useSelector(state => state.cart)
const [loading, setLoading] = useState(0)
useEffect(() => { useEffect(() => {
if (firstName && lastName && shippingAddress && city && state && zipCode) { if (firstName && lastName && shippingAddress && city && state && zipCode) {
...@@ -73,11 +75,14 @@ export default function Checkout() { ...@@ -73,11 +75,14 @@ export default function Checkout() {
userId: user.email, userId: user.email,
cartItems: [] cartItems: []
} }
// clear user cart now that order has been placed // clear user cart now that order has been placed
setLoading(1)
dispatch(updateUserCart(cartUpdateObj, cartUpdateObj.userId)) dispatch(updateUserCart(cartUpdateObj, cartUpdateObj.userId))
dispatch(storeCartItemsInProcessing([])) dispatch(storeCartItemsInProcessing([]))
dispatch(calcTotalPrice([])) dispatch(calcTotalPrice([]))
dispatch(calcNumItems([])) dispatch(calcNumItems([]))
dispatch(calcTotalPromotions([]))
} }
else { else {
setErrorWhileValidating(1) setErrorWhileValidating(1)
...@@ -143,9 +148,11 @@ export default function Checkout() { ...@@ -143,9 +148,11 @@ export default function Checkout() {
{/* Displays info about order. Calculates price with tax / shipping applied */} {/* Displays info about order. Calculates price with tax / shipping applied */}
<ReviewOrder <ReviewOrder
numCartItems={numCartItems} numCartItems={numCartItems}
promotionsDiscount={promotionsDiscount}
itemsTotal={itemsTotal} itemsTotal={itemsTotal}
shippingHandling={shippingHandling} shippingHandling={shippingHandling}
taxRate={taxRate} taxRate={taxRate}
loading={loading}
/> />
{/* Request to submit happens here, initiates input validation and sends if success */} {/* Request to submit happens here, initiates input validation and sends if success */}
<SubmitOrder <SubmitOrder
......
...@@ -27,7 +27,17 @@ export default function CartItem(props) { ...@@ -27,7 +27,17 @@ export default function CartItem(props) {
</div> </div>
{/* Price and promotions */} {/* Price and promotions */}
<div className="priceAndPromotions"> <div className="priceAndPromotions">
<div className="productPrice">${props.productInfo.price.toFixed(2)}</div> {
props.productInfo.promo ?
<div className="productPrice">
<span style={{textDecoration: 'line-through', fontWeight: 'normal'}}>${(props.quantity * props.productInfo.price).toFixed(2)}</span>
<span style={{color: 'green', fontWeight: 'bold'}}>${(props.quantity * props.productInfo.price * (1.0 - parseFloat( props.productInfo.promo / 100.0))).toFixed(2)}</span>
</div>
:
<div className="productPrice">${(props.quantity * props.productInfo.price).toFixed(2)}</div>
}
</div> </div>
</div> </div>
) )
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
padding-top: 25px; padding-top: 25px;
margin-bottom: 15px; margin-bottom: 15px;
font-size: 125%; font-size: 125%;
font-weight: bold;
} }
.productImageContainer { .productImageContainer {
display: flex; display: flex;
...@@ -66,6 +67,8 @@ ...@@ -66,6 +67,8 @@
margin-top: 40px; margin-top: 40px;
font-size: 125%; font-size: 125%;
font-weight: bold; font-weight: bold;
display:flex;
flex-direction: column;
} }
.quantityControls{ .quantityControls{
display: flex; display: flex;
......
...@@ -4,13 +4,13 @@ import './shopping-cart.css' ...@@ -4,13 +4,13 @@ import './shopping-cart.css'
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { Link, Redirect } from 'react-router-dom' import { Link, Redirect } from 'react-router-dom'
import { updateUserCart} from './../../actions/cart_actions' import { updateUserCart} from './../../actions/cart_actions'
import { calcTotalPrice, calcNumItems, storeCartItemsInProcessing } from './../../actions/cart_processing_actions' import { calcTotalPrice, calcNumItems, storeCartItemsInProcessing, calcTotalPromotions } from './../../actions/cart_processing_actions'
export default function ShoppingCart() { export default function ShoppingCart() {
const dispatch = useDispatch() const dispatch = useDispatch()
const userSession = useSelector(state => state.user.currentUser) const userSession = useSelector(state => state.user.currentUser)
const allProducts = useSelector(state => state.market.products) const allProducts = useSelector(state => state.market.products)
const allPromotions = useSelector(state => state.market.promotions)
const [cartRefs, setCartRefs] = useState(useSelector(state => state.cart)) const [cartRefs, setCartRefs] = useState(useSelector(state => state.cart))
const [cartItems, setCartItems] = useState([]) const [cartItems, setCartItems] = useState([])
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
...@@ -66,6 +66,7 @@ export default function ShoppingCart() { ...@@ -66,6 +66,7 @@ export default function ShoppingCart() {
dispatch(storeCartItemsInProcessing(cartItems)) dispatch(storeCartItemsInProcessing(cartItems))
dispatch(calcTotalPrice(cartItems)) dispatch(calcTotalPrice(cartItems))
dispatch(calcNumItems(cartItems)) dispatch(calcNumItems(cartItems))
dispatch(calcTotalPromotions(cartItems))
}, [cartItems]) }, [cartItems])
return ( return (
......
import {GET_TOTAL_PRICE, GET_NUM_ITEMS, GET_CART_PRODUCTS} from './../actions/cart_processing_actions' import {GET_TOTAL_PRICE, GET_NUM_ITEMS, GET_CART_PRODUCTS, GET_TOTAL_PROMOTIONS} from './../actions/cart_processing_actions'
const initialState = { const initialState = {
cartProducts: [], cartProducts: [],
numItems: 0, numItems: 0,
totalPrice: 0 totalPrice: 0,
totalPromotions: 0
} }
const cartProcessingReducer = (prevState = initialState, action) => { const cartProcessingReducer = (prevState = initialState, action) => {
...@@ -25,6 +26,10 @@ const cartProcessingReducer = (prevState = initialState, action) => { ...@@ -25,6 +26,10 @@ const cartProcessingReducer = (prevState = initialState, action) => {
nextState.cartProducts = action.payload nextState.cartProducts = action.payload
return nextState return nextState
case GET_TOTAL_PROMOTIONS:
nextState.totalPromotions = action.payload
return nextState
default: default:
return nextState return nextState
......
...@@ -10,3 +10,25 @@ export const numItemsCalcUtil = (productList) => { ...@@ -10,3 +10,25 @@ export const numItemsCalcUtil = (productList) => {
console.log("ITEMS IN CART: " + totalItems) console.log("ITEMS IN CART: " + totalItems)
return totalItems return totalItems
} }
export const promoCalcUtil = (productList) => {
const totalSavings = productList.reduce( (acc, curr) => {
let temp = (parseFloat(curr.product.price) * parseFloat(curr.quantity))
if (curr.product.promo) {
temp = temp * (parseFloat(curr.product.promo) / 100.0)
return parseFloat(acc) + temp
}
else {
return 0.0
}
}, 0)
console.log("PROMO SAVINGS: " + totalSavings)
return totalSavings
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment