Commit 0ec403fd authored by Kyle Muldoon's avatar Kyle Muldoon

Merge branch 'AFP-46/order-confirmation-page' into 'Dev'

Afp 46/order confirmation page

See merge request !25
parents 91cbb5a3 60e81925
...@@ -6,11 +6,9 @@ import com.nisum.ecomservice.service.ProductService; ...@@ -6,11 +6,9 @@ import com.nisum.ecomservice.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
...@@ -38,4 +36,11 @@ public class ProductsController { ...@@ -38,4 +36,11 @@ public class ProductsController {
return ResponseEntity.ok(productService.getAllPromotions()); return ResponseEntity.ok(productService.getAllPromotions());
} }
@GetMapping("/products/{sku}")
public ResponseEntity<Mono<Product>> getProductBySku(@PathVariable String sku) {
return ResponseEntity.ok(productService.getProductBySku(sku));
}
} }
...@@ -4,7 +4,7 @@ import { sendOrderPost } from './../util/order-api'; ...@@ -4,7 +4,7 @@ import { sendOrderPost } from './../util/order-api';
export const SEND_USER_ORDER = "SEND_USER_ORDER" export const SEND_USER_ORDER = "SEND_USER_ORDER"
const sendUserOrder = (orderConfirmationResponse) => ({ export const sendUserOrder = (orderConfirmationResponse) => ({
type: SEND_USER_ORDER, type: SEND_USER_ORDER,
payload: orderConfirmationResponse payload: orderConfirmationResponse
}) })
......
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import {useSelector} from 'react-redux';
export default function SubmitOrder(props) { export default function SubmitOrder(props) {
const {currentUser} = useSelector(state => state);
let handleSubmitClick = () => { let handleSubmitClick = () => {
console.log("Submit Button Clicked") console.log("Submit Button Clicked")
...@@ -16,9 +19,12 @@ export default function SubmitOrder(props) { ...@@ -16,9 +19,12 @@ export default function SubmitOrder(props) {
<p>Step 4 - Checkout</p> <p>Step 4 - Checkout</p>
</div> </div>
<div id="SubmitButtonContainer"> <div id="SubmitButtonContainer">
<p className="errorMessage">{(props.errorWhileValidating === 1 && props.allFieldsValidated === 0)? 'One or more fields is missing a value' : ''}</p> <p className="errorMessage">{(props.errorWhileValidating === 1 && props.allFieldsValidated === 0)? 'One or more fields is missing a value' : ''}</p>
<button id="SubmitButtonInput" onClick={() => {handleSubmitClick()}} >Submit Order</button> <button
id="SubmitButtonInput"
onClick={() => {handleSubmitClick()}}
>Submit Order</button>
</div> </div>
</div> </div>
......
...@@ -7,7 +7,8 @@ import ReviewOrder from './ReviewOrder.js' ...@@ -7,7 +7,8 @@ import ReviewOrder from './ReviewOrder.js'
import SubmitOrder from './SubmitOrder.js' import SubmitOrder from './SubmitOrder.js'
import './checkout.css' import './checkout.css'
import { dispatchOrderInfo } from './../../actions/checkout_actions' import {dispatchOrderInfo} from './../../actions/checkout_actions'
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 } from './../../actions/cart_processing_actions'
...@@ -143,6 +144,10 @@ export default function Checkout() { ...@@ -143,6 +144,10 @@ export default function Checkout() {
} }
//redirect to order confirmation page once reponse is recieved
const {orderResponse} = useSelector(state => state.orderStatus);
if (orderResponse.id) return <Redirect to="/order-confirmation" />
return ( return (
<div id="checkout-container"> <div id="checkout-container">
......
main {
margin: 0 auto;
max-width: 1080px;
}
#order-confirmation-header {
font-size: 40px;
text-align: center;
padding: 10px;
}
#return-to-martket {
margin-top: 50px;
display: flex;
justify-content: center;
width: 100%;
}
\ No newline at end of file
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Redirect } from 'react-router';
import {NavLink} from 'react-router-dom';
import {sendUserOrder} from '../../actions/checkout_actions'
import Order from '../order-history/Order';
import './order-confirmation.css'
const OrderConfirmation = () => {
const dispatch = useDispatch();
const {orderResponse} = useSelector(state => state.orderStatus);
useEffect(()=>{
return () => dispatch(sendUserOrder({}));
},[])
const orderPlaced = new Date(orderResponse.orderCreatedAt);
if (!orderResponse.id) return (<Redirect to="/product-market" />) //once we have flow from place order page, this can be uncommented
return (
<main>
<h1 id="order-confirmation-header">Thanks for your order!</h1>
<Order order={orderResponse} alt={0}/>
<div id="return-to-market">
<NavLink to="/product-market"> Return To Market</NavLink>
</div>
</main>
);
}
export default OrderConfirmation;
\ No newline at end of file
...@@ -5,28 +5,15 @@ import OrderItem from './OrderItem'; ...@@ -5,28 +5,15 @@ import OrderItem from './OrderItem';
const Order = (props) => { const Order = (props) => {
const {order} = props; const {order, alt} = props;
const {orderItems, customerAddress} = order; const {orderItems, customerAddress} = order;
const orderTotalPrice = totalPrice(orderItems); const orderTotalPrice = totalPrice(orderItems);
const orderPlaced = new Date(order.orderCreatedAt); const orderPlaced = new Date(order.orderCreatedAt);
return ( return (
<div className="order"> <div className={`order order-${alt%2}`}>
<p><span className="order-field">Order ID:</span> {order.id}</p> <p className="order-number">Order #: {order.id}</p>
<p><span className="order-field">Order Status:</span> {order.orderStatus}</p>
<p><span className="order-field">Order Placed:</span> {orderPlaced.getUTCMonth()}
/{orderPlaced.getUTCDay()}
/{orderPlaced.getUTCFullYear()}
</p>
<p>
<span className="order-field">Delivery Address:</span> {`
${customerAddress.street},
${customerAddress.city},
${customerAddress.state} ${customerAddress.zip}
`}
</p>
<p><span className="order-field">Items Ordered:</span></p>
<div className="products-ordered"> <div className="products-ordered">
{orderItems.map((orderItem, ind) => { {orderItems.map((orderItem, ind) => {
return ( return (
...@@ -34,8 +21,27 @@ const Order = (props) => { ...@@ -34,8 +21,27 @@ const Order = (props) => {
) )
})} })}
</div> </div>
<div className="total-price">
<p><span className="order-field">Total Price:</span> ${orderTotalPrice}</p> <p><span className="order-field">Total Price:</span> ${orderTotalPrice}</p>
</div>
<div className="order-details">
<div className="order-description">
<p className="order-status-header">Status</p>
<p><span className="order-field">Order Placed:</span> {orderPlaced.getUTCMonth()}
/{orderPlaced.getUTCDay()}
/{orderPlaced.getUTCFullYear()}
</p>
<p><span className="order-field">Order Status:</span> {order.orderStatus}</p>
</div>
<div className="shipping-details">
<p className="shipping-details-header">Shipping Address</p>
<span>{customerAddress.street},</span>
<p>{`${customerAddress.city},
${customerAddress.state} ${customerAddress.zip}
`}
</p>
</div>
</div>
</div> </div>
) )
} }
......
import React from 'react'; import React, { useEffect,useState } from 'react';
import { fetchProductBySku } from '../../util/product_api_util';
const OrderItem = (props) => { const OrderItem = (props) => {
const {orderItem} = props; const {orderItem} = props;
const {itemName, itemPrice, itemQuantity} = orderItem; const {itemName, itemPrice, itemQuantity, itemSku} = orderItem;
const subTotal = itemQuantity * itemPrice; const subTotal = itemQuantity * itemPrice;
const [fullItemDetails, setFullItemDetails] = useState({});
useEffect(async ()=>{
const item = await fetchProductBySku(itemSku);
setFullItemDetails(item.data);
}, []);
return ( return (
<div className="order-item"> <div className="order-item">
<p>Item: {itemName}</p> <div className="pic-container">
<p>Quantity: {itemQuantity}</p> <img className="order-item-pic" src={fullItemDetails.productImageUrl} />
<p>Price Per Unit: ${itemPrice.toFixed(2)}</p> </div>
<p>Subtotal: ${subTotal.toFixed(2)}</p> <div className="item-details-container">
<p className="order-item-name">{itemName}</p>
<p className="order-item-quantity">QTY: {itemQuantity}</p>
</div>
<div className="item-price-container">
<p>Price Per Unit: ${itemPrice.toFixed(2)}</p>
<p className="order-subtotal">Subtotal: ${subTotal.toFixed(2)}</p>
</div>
</div> </div>
) )
} }
......
...@@ -6,8 +6,8 @@ main { ...@@ -6,8 +6,8 @@ main {
.order { .order {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 1px solid black;
padding: 20px; padding: 20px;
margin-bottom: 10px;
} }
.products-ordered { .products-ordered {
...@@ -16,12 +16,16 @@ main { ...@@ -16,12 +16,16 @@ main {
} }
.order-item { .order-item {
border: 1px solid gray; display: grid;
padding: 5px; grid-template-columns: 1fr 4fr 1fr;
grid-template-rows: auto;
border-bottom: 1px solid lightgray;
width: 100%;
} }
.order-field { .order-number {
font-weight: 600; font-weight: 600;
border-bottom: 1px solid black;
} }
#order-history-header { #order-history-header {
...@@ -29,3 +33,72 @@ main { ...@@ -29,3 +33,72 @@ main {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
} }
.order-item-pic {
width: 200px;
height: 200px;
padding: 25px;
}
.item-details-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.order-item-name {
font-size: 24px;
}
.order-item-quantity {
color: gray;
}
.item-price-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.order-subtotal {
font-weight: 600;
}
.order-details {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
}
.total-price {
display: flex;
justify-content: flex-end;
border-bottom: 1px solid lightgray;
padding: 20px;
font-weight: 600;
font-size: 20px;
}
.order-status-header, .shipping-details-header {
font-weight: 600;
border-bottom: 1px solid black ;
width: fit-content;
margin-bottom: 10px;
font-size: 16px;
margin-top: 10px;
}
.shipping-details, .order-description {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.order-1 {
background-color: #f0f0f0;
}
\ No newline at end of file
...@@ -25,12 +25,12 @@ const OrderHistory = () => { ...@@ -25,12 +25,12 @@ const OrderHistory = () => {
return ( return (
<main> <main>
<h1 id="order-history-header">Order History</h1> <h1 id="order-history-header">Order History</h1>
{user === null ? ( {user === null || user.currentUser === null ? (
<div>Please login to place orders!</div> <div>Please login to place orders!</div>
) : null} ) : null}
<div id="orders"> <div id="orders">
{orderHistory.map(order => { {orderHistory.map((order, ind) => {
return <Order order={order}/>; return <Order order={order} key={ind} alt={ind}/>;
})} })}
</div> </div>
</main> </main>
......
...@@ -7,6 +7,7 @@ import ShoppingCartContainer from './shopping-cart/shopping-cart-container'; ...@@ -7,6 +7,7 @@ import ShoppingCartContainer from './shopping-cart/shopping-cart-container';
import CheckoutContianer from './checkout/checkout-container'; import CheckoutContianer from './checkout/checkout-container';
import Header from './Header/header-container' import Header from './Header/header-container'
import OrderHistory from './order-history/order-history'; import OrderHistory from './order-history/order-history';
import OrderConfirmation from './order-confirmation/order-confirmation';
const Root = ({ store }) => ( const Root = ({ store }) => (
<Provider store={ store }> <Provider store={ store }>
...@@ -24,6 +25,7 @@ const Root = ({ store }) => ( ...@@ -24,6 +25,7 @@ const Root = ({ store }) => (
<Route path="/cart" component={ ShoppingCartContainer } /> <Route path="/cart" component={ ShoppingCartContainer } />
<Route path="/checkout" component={ CheckoutContianer } /> <Route path="/checkout" component={ CheckoutContianer } />
<Route path="/orders" component={ OrderHistory } /> <Route path="/orders" component={ OrderHistory } />
<Route path="/order-confirmation" component= {OrderConfirmation} />
</Switch> </Switch>
</BrowserRouter> </BrowserRouter>
</Provider> </Provider>
......
...@@ -90,7 +90,7 @@ export default function ShoppingCart() { ...@@ -90,7 +90,7 @@ export default function ShoppingCart() {
}, [cartItems]) }, [cartItems])
if (!userSession) return <Redirect to="/product-market" /> //if (!userSession) return <Redirect to="/product-market" />
return ( return (
<div id="shoppingCartContainer"> <div id="shoppingCartContainer">
...@@ -110,9 +110,16 @@ export default function ShoppingCart() { ...@@ -110,9 +110,16 @@ export default function ShoppingCart() {
})} })}
</div> </div>
<Link id="checkoutButtonContainer" to="/checkout"> {
<button id="checkoutButton">Proceed To Checkout</button> cartItems.length > 0 ? (
</Link> <Link id="checkoutButtonContainer" to="/checkout">
<button id="checkoutButton">Proceed To Checkout</button>
</Link>
) : (
<div>Add more items to your cart!</div>
)
}
</div> </div>
......
...@@ -11,4 +11,8 @@ export const fetchPromotions = () => { ...@@ -11,4 +11,8 @@ export const fetchPromotions = () => {
export const fetchProductsAndPromotions = () => { export const fetchProductsAndPromotions = () => {
return axios.get(`${Config.baseApiUrl}/api/products-and-promos`) return axios.get(`${Config.baseApiUrl}/api/products-and-promos`)
}
export const fetchProductBySku = (sku) => {
return axios.get(`${Config.baseApiUrl}/api/products/${sku}`)
} }
\ 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