Commit 6cf009ec authored by Xiyang Lu's avatar Xiyang Lu

Merge branch 'master' of https://gitlab.mynisum.com/ascend/ecommerce-maven...

Merge branch 'master' of https://gitlab.mynisum.com/ascend/ecommerce-maven into AFP-80-Header-Upgrade
ly if it merges an updated upstream into a topic branch.
parents 0ece723c fdecf06b
apiVersion: apps/v1
kind: Deployment
metadata:
name: afp-ecom-api-deployment
spec:
replicas: 2
selector:
matchLabels:
app: afp-ecom-api
template:
metadata:
labels:
app: afp-ecom-api
spec:
containers:
- name: afp-ecom-api-container
image: nexus.mynisum.com/afp-ecom-api:11
imagePullPolicy: Always
ports:
- containerPort: 8084
env:
- name: PORT
value: "8080"
imagePullSecrets:
- name: registry-creds
---
apiVersion: v1
kind: Service
metadata:
name: afp-ecom-api-service
spec:
type: LoadBalancer
ports:
- port: 8084
targetPort: 8080
selector:
app: afp-ecom-api
...@@ -8,6 +8,9 @@ import org.springframework.stereotype.Service; ...@@ -8,6 +8,9 @@ import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -24,7 +27,6 @@ public class OrderService { ...@@ -24,7 +27,6 @@ public class OrderService {
User user = orderRequest.getUser(); User user = orderRequest.getUser();
Address address = orderRequest.getAddress(); Address address = orderRequest.getAddress();
CartPostDTO cart = orderRequest.getCart(); CartPostDTO cart = orderRequest.getCart();
//for each item grab product details from products API //for each item grab product details from products API
List<Mono<Product>> productsToOrder = cart.getCartItems().stream() List<Mono<Product>> productsToOrder = cart.getCartItems().stream()
.map(cartItem -> cartItem.getProductRef().getSku()) .map(cartItem -> cartItem.getProductRef().getSku())
...@@ -50,8 +52,27 @@ public class OrderService { ...@@ -50,8 +52,27 @@ public class OrderService {
orderItem.setItemQuantity(quantity); orderItem.setItemQuantity(quantity);
return orderItem; return orderItem;
}).flatMap(orderItem -> {
Flux<Promotion> promotionFlux = productService
.getPromotionBySkus(Arrays.asList(orderItem.getItemSku()));
Mono<OrderItem> orderItemMono = promotionFlux
.collectList()
.map(promotionList -> {
System.out.println("Give me promo list");
if (promotionList.size() > 0) {
Promotion promotion = promotionList.get(0);
if (orderItem.getItemQuantity().intValue() >= promotion.getMinimumQuantity().intValue()) {
Float originalPrice = orderItem.getItemPrice();
Float newPrice = originalPrice * ((100 - promotion.getDiscountPercentage()) / 100);
orderItem.setItemPrice(newPrice);
}
}
return orderItem;
});
return orderItemMono;
}); });
// //falttens the things
Mono<Mono<Order>> map = orderItems.collectList().map(itemList -> { Mono<Mono<Order>> map = orderItems.collectList().map(itemList -> {
OrderSubmission orderSubmission = new OrderSubmission(); OrderSubmission orderSubmission = new OrderSubmission();
......
...@@ -8,6 +8,8 @@ import reactor.core.publisher.Mono; ...@@ -8,6 +8,8 @@ import reactor.core.publisher.Mono;
import com.nisum.ecomservice.model.Promotion; import com.nisum.ecomservice.model.Promotion;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.List;
@Service @Service
public class ProductService { public class ProductService {
...@@ -40,4 +42,12 @@ public class ProductService { ...@@ -40,4 +42,12 @@ public class ProductService {
.bodyToFlux(Promotion.class); .bodyToFlux(Promotion.class);
} }
public Flux<Promotion> getPromotionBySkus(List<String> listOfSkus){
return WebClient.create(String.format("%s/api/promos/bulkSearch",AppConfig.getPromoManagementUrl()))
.post()
.bodyValue(listOfSkus)
.retrieve()
.bodyToFlux(Promotion.class);
}
} }
// import util api call for cart order
import { sendOrderPost } from './../util/order-api';
export const SEND_USER_ORDER = "SEND_USER_ORDER"
const sendUserOrder = (orderConfirmationResponse) => ({
type: SEND_USER_ORDER,
payload: orderConfirmationResponse
})
export const dispatchOrderInfo = (orderInfo) => dispatch => sendOrderPost(orderInfo)
.then( response => dispatch(sendUserOrder(response.data)) )
import OrderHistory from "../components/order-history/order-history";
import { getOrderHistory } from "../util/order_api_util";
export const SET_USER_HISTORY = "SET_USER_HISTORY";
export const dispatchOrderHistory = (userId) => async dispatch =>{
const orderHistory = await getOrderHistory(userId);
return dispatch(orderHistoryAction(orderHistory));
}
const orderHistoryAction = (orderHistory) => (
{
type: SET_USER_HISTORY,
orderHistory
}
)
\ No newline at end of file
// import React, {useState, useEffect} from 'react' // import React, {useState, useEffect} from 'react'
export default function PaymentMethod() { export default function PaymentMethod(props) {
return ( return (
<div> <div className="order-component" id="PaymentInput">
PAYMENT METHOD COMPONENT
<div className="OrderDirectionsElement" id="PaymentLabel">
<p>Step 2 - Billing</p>
</div>
<div className="shippingInfoInput" id="cardNumberInput">
<input placeholder={props.cardNumber}
type="text"
onChange={(e) => props.captureCardNumber(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="cardholderNameInput">
<input placeholder={props.cardHolderName}
type="text"
onChange={(e) => props.captureCardHolderName(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="expirationDateInput">
<input placeholder={props.expirationDate}
type="text"
onChange={(e) => props.captureExpirationDate(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="cvvCodeInput">
<input placeholder={props.cvv}
type="text"
onChange={(e) => props.captureCVV(e.target.value)}
className="input-text"
></input>
</div>
</div> </div>
) )
} }
// import React, {useState, useEffect} from 'react' import React, { useState, useEffect } from 'react'
export default function ReviewOrder(props) {
let calcTotalBeforeTax = (itemsTotal, shippingTotal) => {
return parseFloat(itemsTotal) + parseFloat(shippingTotal)
}
let calcTaxAmount = (itemsTotal, shippingTotal, taxRate) => {
return calcTotalBeforeTax(itemsTotal, shippingTotal) * taxRate
}
let calcGrandTotal = (itemsTotal, shippingTotal, taxRate) => {
return calcTotalBeforeTax(itemsTotal, shippingTotal) * (parseFloat(1.00) + parseFloat(taxRate))
}
export default function ReviewOrder() {
return ( return (
<div className="order-component" id="ReviewOrder">
<div className="OrderDirectionsElement" id="OrderSummary">
<p>Step 3 - Order Summary</p>
</div>
<div id="orderSummaryContainer">
<div className="OrderSummaryElement" id="itemsPrice">
<div>
Items ({props.numCartItems}):
</div>
<div> <div>
REVIEW ORDER COMPONENT ${parseFloat(props.itemsTotal).toFixed(2)}
</div>
</div>
<div className="OrderSummaryElement" id="shippingHandling">
<div id="shippingHandlingLabel">
Shipping and Handling:
</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>
) )
} }
// import React, {useState, useEffect} from 'react' // import React, {useState, useEffect} from 'react'
export default function ShippingAddress() { export default function ShippingAddress(props) {
return ( return (
<div> <div className="order-component" id="shippingInput">
SHIPPING ADDRESS COMPONENT
<div className="OrderDirectionsElement" id="ShippingLabel">
<p>Step 1 - Shipping</p>
</div>
<div className="shippingInfoInput" id="firstNameInput">
<input placeholder="First Name*"
type="text"
onChange={(e) => props.captureFirstName(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="lastNameInput">
<input placeholder="Last Name*"
type="text"
onChange={(e) => props.captureLastName(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="addressInput">
<input placeholder="Shipping Address*"
type="text"
onChange={(e) => props.captureShippingAddress(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="aptSuiteInput">
<input placeholder="Apt. / Suite no"
type="text"
onChange={(e) => props.captureAptSuiteNo(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="cityInput">
<input placeholder="City*"
type="text"
onChange={(e) => props.captureCity(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="stateInput">
<input placeholder="State*"
type="text"
onChange={(e) => props.captureState(e.target.value)}
className="input-text"
></input>
</div>
<div className="shippingInfoInput" id="zipCodeInput">
<input placeholder="Postal Code*"
type="text"
onChange={(e) => props.captureZipCode(e.target.value)}
className="input-text"
></input>
</div>
</div> </div>
) )
} }
// import React, {useState, useEffect} from 'react' import React, { useState, useEffect } from 'react'
export default function SubmitOrder(props) {
let handleSubmitClick = (e) => {
console.log("Submit Button Clicked")
props.setSubmitButtonActive(1)
}
export default function SubmitOrder() {
return ( return (
<div> <div className="order-component" id="SubmitOrder">
SUBMIT ORDER COMPONENT
<div className="OrderDirectionsElement" id="SubmitLabel">
<p>Step 4 - Checkout</p>
</div>
<div id="SubmitButtonContainer">
<p className="errorMessage">{(props.errorWhileValidating === 1 && props.allFieldsValidated[0] === 0)? 'One or more fields is missing a value' : ''}</p>
<button id="SubmitButtonInput" onClick={handleSubmitClick} >Submit Order</button>
</div>
</div> </div>
) )
} }
* {
font-family: Arial, sans-serif;
}
#checkout-container {
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.OrderDirectionsElement {
font-size: 150%;
font-weight: bold;
}
.shippingInfoInput {
margin-bottom: 10px;
}
.input-text {
height: 40px;
width: 100%;
padding-left: 20px;
font-size: 120%;
}
.order-component {
border-top: 2px solid black;
min-width: 60%;
margin-bottom: 50px;
}
#orderSummaryContainer {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
justify-content: center;
margin-top: 50px;
}
.OrderSummaryElement {
display: flex;
flex-direction: row;
width: 40%;
justify-content: space-between;
margin-bottom: 10px;
font-size: 150%;
}
#orderTotal {
border-top: 2px solid black;
padding-top: 10px;
font-weight: bolder;
color: rgb(223, 5, 5);
}
#SubmitButtonContainer {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-bottom: 100px;
margin-top: 75px;
}
#SubmitButtonInput {
width: 75%;
color: white;
background-color: black;
height: 80px;
font-size: 200%;
}
.errorMessage {
color: red;
font-weight: bold;
font-size: 120%;
}
\ No newline at end of file
import React, {useState, useEffect} from 'react'
import {useSelector, useDispatch} from 'react-redux'
import ShippingAddress from './ShippingAddress.js'
import PaymentMethod from './PaymentMethod.js'
import ReviewOrder from './ReviewOrder.js'
import SubmitOrder from './SubmitOrder.js'
import './checkout.css'
import {dispatchOrderInfo} from './../../actions/checkout_actions'
export default function Checkout() {
///////////////////////
// REDUX Global State
///////////////////////
// const {currentUser} = useSelector(state => state)
// const {cart} = useSelector(state => state)
///////////////////////
// Shipping Info State
///////////////////////
const [firstName, setFirstName] = useState([])
const [lastName, setLastName] = useState([])
const [shippingAddress, setShippingAddress] = useState([])
const [aptSuiteNo, setAptSuiteNo] = useState([])
const [city, setCity] = useState([])
const [state, setState] = useState([])
const [zipCode, setZipCode] = useState([])
///////////////////////
// Billing Info State
///////////////////////
const [cardNumber, setCardNumber] = useState(["123456789"])
const [cardHolderName, setCardHolderName] = useState(["Jane Doe"])
const [expirationDate, setExpirationDate] = useState(["05/20206"])
const [cvv, setCVV] = useState(["123"])
///////////////////////
// Order Review / Summary State
///////////////////////
const [numCartItems, setNumCartItems] = useState([3])
const [itemsTotal, setItemsTotal] = useState([87.65]) // arbitrary cart total
const [shippingHandling, setShippingHandling] = useState([8.00]) // arbitraty shipping and handling amount
const [taxRate, setTaxRate] = useState([0.0725]) // california tax rate
///////////////////////
// Submit Button State
///////////////////////
const [submitButtonActive, setSubmitButtonActive] = useState([0])
const [allFieldsValidated, setAllFieldsValidated] = useState([0])
const [errorWhileValidating, setErrorWhileValidating] = useState([0])
///////////////////////
// Submit Action State
///////////////////////
const dispatch = useDispatch()
// This effect listens for the submit button to be clicked
// It then checks each of the required fields to make sure they are non null
useEffect( () => {
// when submit button clicked after being inactive
if (submitButtonActive === 1) {
// fields to make sure are non-empty
let fieldsToCheck = [
firstName,
lastName,
shippingAddress,
city,
state,
zipCode,
// cardNumber,
// cardHolderName,
// expirationDate,
// cvv
]
// initially validated, gets flipped if required field is empty
let validated = 1
for (let i in fieldsToCheck) {
if (fieldsToCheck[i].length === 0) {
console.log("a required Field Value was empty")
validated = 0
break
}
}
// if validated, propogate to component state that all fields were validated
if (validated === 1) {
setAllFieldsValidated(1)
}
// if invalid, propogate to component state
else {
console.log("Failed to validate")
setErrorWhileValidating(1)
setSubmitButtonActive(0)
}
}
// when submit button gets reset from active to inactive
else if (submitButtonActive === 0) {
;
}
}, [submitButtonActive])
// This effect looks for the allFieldsValidated signal and fires off the order submission when complete
useEffect( () => {
handleSubmit()
console.log("Successful submission request")
}, [allFieldsValidated])
let handleSubmit = () => {
console.log("Submitting Order!")
// let order = {}
// order["firstName"] = firstName
// order["lastName"] = lastName
// order["shippingAddress"] = shippingAddress
// order["aptSuitNo"] = aptSuiteNo
// order["city"] = city
// order["state"] = state
// order["zipCode"] = zipCode
// order["cardNumber"] = cardNumber
// order["cardHolderName"] = cardHolderName
// order["expirationDate"] = expirationDate
// order["cvv"] = cvv
// console.log(order)
let chrisSpec = {
"user": {
"userId": "e-com-test-id",
"email": "test@test.test",
"firstName":"ecom",
"lastName":"test"
},
"address": {
"state": "IL",
"city": "chicago",
"zip": "90210",
"street": "Grand"
},
"cart": {
"id": "something",
"userId": "e-com-test-id",
"cartItems":[
{
"quantity": 2,
"productRef": {
"id": "something",
"sku": "AFP-1",
"upc": "00002"
}
}
]
}
}
dispatch(dispatchOrderInfo(chrisSpec))
}
// This watches for changes on any shipping input fields and prints the current states on any change
useEffect( () => {
console.log("======================================")
console.log("First Name: " + firstName)
console.log("Last Name: " + lastName)
console.log("Shipping Address: " + shippingAddress)
console.log("Apt Suite No: " + aptSuiteNo)
console.log("City: " + city)
console.log("State: " + state)
console.log("Zip: " + zipCode)
}, [firstName, lastName, shippingAddress, aptSuiteNo, city, state, zipCode])
// This watches for changes on any billing input fields and prints the current states on any change
useEffect( () => {
console.log("======================================")
console.log("Card Number: " + cardNumber)
console.log("Card Holder Name: " + cardHolderName)
console.log("Expiration Date: " + expirationDate)
console.log("CVV: " + cvv)
}, [cardNumber, cardHolderName, expirationDate, cvv])
return (
<div id="checkout-container">
{/* Collects User's shipping info */}
<ShippingAddress
captureFirstName={setFirstName}
captureLastName={setLastName}
captureShippingAddress={setShippingAddress}
captureAptSuiteNo={setAptSuiteNo}
captureCity={setCity}
captureState={setState}
captureZipCode={setZipCode}
/>
{/* Collects User's payment info */}
<PaymentMethod
cardNumber={cardNumber}
cardHolderName={cardHolderName}
expirationDate={expirationDate}
cvv={cvv}
captureCardNumber={setCardNumber}
captureCardHolderName={setCardHolderName}
captureExpirationDate={setExpirationDate}
captureCVV={setCVV}
/>
{/* Displays info about order. Calculates price with tax / shipping applied */}
<ReviewOrder
numCartItems={numCartItems}
itemsTotal={itemsTotal}
shippingHandling={shippingHandling}
taxRate={taxRate}
/>
{/* Request to submit happens here, initiates input validation and sends if success */}
<SubmitOrder
submitButtonActive={submitButtonActive}
setSubmitButtonActive={setSubmitButtonActive}
errorWhileValidating={errorWhileValidating}
allFieldsValidated={allFieldsValidated}
/>
</div>
)
}
// import React, {useState, useEffect} from 'react'
// import ShippingAddress from './ShippingAddress.js'
// import PaymentMethod from './PaymentMethod.js'
// import ReviewOrder from './ReviewOrder.js'
// import SubmitOrder from './SubmitOrder.js'
export default function checkout() {
return (
<div>
</div>
)
}
/*
1. Shipping Address
2. Payment Method
3. Review Order
4. Submit Order
*/
\ No newline at end of file
...@@ -23,3 +23,9 @@ main { ...@@ -23,3 +23,9 @@ main {
.order-field { .order-field {
font-weight: 600; font-weight: 600;
} }
#order-history-header {
font-size: 40px;
text-align: center;
padding: 10px;
}
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { dispatchOrderHistory } from '../../actions/order_history_actions';
import { getOrderHistory } from '../../util/order_api_util'; import { getOrderHistory } from '../../util/order_api_util';
import Order from './Order'; import Order from './Order';
...@@ -6,20 +8,26 @@ import './order-history.css' ...@@ -6,20 +8,26 @@ import './order-history.css'
const OrderHistory = () => { const OrderHistory = () => {
const [orderHistory, setOrderHistory] = useState([]); const dispatch = useDispatch();
const [callMade, setCallMade] = useState(false);
const user = useSelector(state => state.user);
const {orderHistory} = useSelector(state => state.orderStatus)
//const [callMade, setCallMade] = useState(false);
useEffect( async () => { useEffect( async () => {
if (callMade) return; if (!user) return;
const userHistory = await getOrderHistory('e-com-test-id2'); //insert user id from user details stored in redux if (!user.currentUser) return;
setOrderHistory(userHistory); dispatch(dispatchOrderHistory(user.currentUser.userId));
setCallMade(true); //setCallMade(true);
}); }, [user]);
return ( return (
<main> <main>
<h1>Order History</h1> <h1 id="order-history-header">Order History</h1>
{user === null ? (
<div>Please login to place orders!</div>
) : null}
<div id="orders"> <div id="orders">
{orderHistory.map(order => { {orderHistory.map(order => {
return <Order order={order}/>; return <Order order={order}/>;
......
import { SET_USER_HISTORY } from '../actions/order_history_actions';
import { LOGOUT_USER } from '../actions/session_actions';
import {SEND_USER_ORDER} from './../actions/checkout_actions'
const initialState = {
orderResponse: {},
orderHistory: []
}
const orderReducer = (prevState = initialState, action) => {
Object.freeze(prevState);
const nextState = {...prevState};
switch(action.type) {
case SEND_USER_ORDER:
nextState.orderResponse = action.payload
return nextState
case SET_USER_HISTORY:
nextState.orderHistory = action.orderHistory;
return nextState;
case LOGOUT_USER:
nextState.orderHistory = [];
return nextState;
default:
return nextState
}
}
export default orderReducer
\ No newline at end of file
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import productsReducer from './products_reducer'; import productsReducer from './products_reducer';
import orderReducer from './order_reducer'
import userReducer from './user_reducer' import userReducer from './user_reducer'
import cartReducer from './cart_reducer'; import cartReducer from './cart_reducer';
const rootReducer = combineReducers({ const rootReducer = combineReducers({
market: productsReducer, market: productsReducer,
user: userReducer, user: userReducer,
cart: cartReducer cart: cartReducer,
orderStatus: orderReducer
}); });
export default rootReducer; export default rootReducer;
\ No newline at end of file
import axios from 'axios'
export const sendOrderPost = (order) => {
return axios.post("http://localhost:8080/api/orders", order)
}
\ No newline at end of file
...@@ -2,37 +2,36 @@ import Config from "../config"; ...@@ -2,37 +2,36 @@ import Config from "../config";
export const getOrderHistory = async (userId) => { export const getOrderHistory = async (userId) => {
//This is what we will return once our oder history API is operational const res = await fetch(`${Config.orderHistoryApiUrlMethod(userId)}`);
// const res = await fetch(`${Config.orderHistoryApiUrlMethod(userId)}`); const orders = await res.json();
// const orders = await res.json(); return orders;
// return orders;
return ( // return (
[ // [
{ // {
id: "609968a89cc32e1ef8208e8b", // id: "609968a89cc32e1ef8208e8b",
orderTrackingCode: "N/A", // orderTrackingCode: "N/A",
orderStatus: "RECEIVED", // orderStatus: "RECEIVED",
orderCreatedAt: 1620666536727, // orderCreatedAt: 1620666536727,
orderUpdatedAt: 1620666536727, // orderUpdatedAt: 1620666536727,
customerId: "e-com-test-id2", // customerId: "e-com-test-id2",
customerEmailAddress: "test@test.test", // customerEmailAddress: "test@test.test",
orderItems: [ // orderItems: [
{ // {
itemId: "AFP-989", // itemId: "AFP-989",
itemName: "this item", // itemName: "this item",
itemSku: "AFP-989", // itemSku: "AFP-989",
itemQuantity: 2, // itemQuantity: 2,
itemPrice: 14.0 // itemPrice: 14.0
} // }
], // ],
customerAddress: { // customerAddress: {
street: "Grand", // street: "Grand",
city: "chicago", // city: "chicago",
state: "IL", // state: "IL",
zip: "90210" // zip: "90210"
} // }
} // }
] // ]
) // )
} }
\ 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