Commit 88b9f764 authored by Kyle Muldoon's avatar Kyle Muldoon

merged Dev into master for deployment. Backend now looking into cloud services...

merged Dev into master for deployment. Backend now looking into cloud services instead of local servers. Bugfixes (lots)
parent cc6a4363
package com.nisum.ecomservice.config;
public class AppConfig {
private static final String orderManagementUrl = "http://localhost:8084";
private static final String productsManagementUrl = "http://localhost:8083";
private static final String promoManagementUrl = "http://localhost:8082";
public static String getOrderManagementUrl() {
return orderManagementUrl;
}
public static String getProductsManagementUrl() {
return productsManagementUrl;
}
public static String getPromoManagementUrl() {
return promoManagementUrl;
}
}
...@@ -18,8 +18,8 @@ public class CartController { ...@@ -18,8 +18,8 @@ public class CartController {
@Autowired @Autowired
private CartService cartService; private CartService cartService;
@Autowired // @Autowired
private CartRepository cartRepository; // private CartRepository cartRepository;
@GetMapping("/api/carts") @GetMapping("/api/carts")
public ResponseEntity<Flux<CartDTO>>getAllUserCarts(){ public ResponseEntity<Flux<CartDTO>>getAllUserCarts(){
......
...@@ -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));
}
} }
package com.nisum.ecomservice.service; package com.nisum.ecomservice.service;
import com.nisum.ecomservice.config.AppConfig;
import com.nisum.ecomservice.dto.*; import com.nisum.ecomservice.dto.*;
import com.nisum.ecomservice.model.*; import com.nisum.ecomservice.model.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; 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.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -20,6 +19,8 @@ public class OrderService { ...@@ -20,6 +19,8 @@ public class OrderService {
@Autowired @Autowired
ProductService productService; ProductService productService;
@Value("${orders.apiUrl}")
private String orderManagementUrl;
public Mono<Order> postOrder(OrderRequest orderRequest) { public Mono<Order> postOrder(OrderRequest orderRequest) {
...@@ -93,7 +94,7 @@ public class OrderService { ...@@ -93,7 +94,7 @@ public class OrderService {
} }
public Mono<Order> postOrderToAPI(OrderSubmission orderSubmission){ public Mono<Order> postOrderToAPI(OrderSubmission orderSubmission){
return WebClient.create(String.format("%s/api/orders", AppConfig.getOrderManagementUrl())) return WebClient.create(String.format("%s/api/orders", orderManagementUrl))
.post() .post()
.bodyValue(orderSubmission) .bodyValue(orderSubmission)
.retrieve() .retrieve()
...@@ -101,7 +102,7 @@ public class OrderService { ...@@ -101,7 +102,7 @@ public class OrderService {
} }
public Flux<Order> getOrderFromOmsAPI(String userId){ public Flux<Order> getOrderFromOmsAPI(String userId){
return WebClient.create(String.format("%s/api/orders/byCustomer/%s", AppConfig.getOrderManagementUrl(),userId)) return WebClient.create(String.format("%s/api/orders/byCustomer/%s", orderManagementUrl,userId))
.get() .get()
.retrieve() .retrieve()
.bodyToFlux(Order.class); .bodyToFlux(Order.class);
......
package com.nisum.ecomservice.service; package com.nisum.ecomservice.service;
import com.nisum.ecomservice.config.AppConfig;
import com.nisum.ecomservice.model.Product; import com.nisum.ecomservice.model.Product;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; 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.Mono; import reactor.core.publisher.Mono;
...@@ -13,8 +13,15 @@ import java.util.List; ...@@ -13,8 +13,15 @@ import java.util.List;
@Service @Service
public class ProductService { public class ProductService {
@Value("${products.apiUrl}")
private String productsManagementUrl;
@Value("${promos.apiUrl}")
private String promoManagementUrl;
public Mono<Product> getProductBySku(String sku){ public Mono<Product> getProductBySku(String sku){
return WebClient.create(String.format("%s/api/products/%s", AppConfig.getProductsManagementUrl(),sku)) return WebClient.create(String.format("%s/api/products/%s", productsManagementUrl,sku))
.get() .get()
.retrieve() .retrieve()
.bodyToMono(Product.class); .bodyToMono(Product.class);
...@@ -23,7 +30,7 @@ public class ProductService { ...@@ -23,7 +30,7 @@ public class ProductService {
public Flux<Product> getAllProducts() { public Flux<Product> getAllProducts() {
return WebClient return WebClient
.builder() .builder()
.baseUrl(AppConfig.getProductsManagementUrl()) .baseUrl(productsManagementUrl)
.build() .build()
.get() .get()
.uri("/api/products") .uri("/api/products")
...@@ -34,7 +41,7 @@ public class ProductService { ...@@ -34,7 +41,7 @@ public class ProductService {
public Flux<Promotion> getAllPromotions() { public Flux<Promotion> getAllPromotions() {
return WebClient return WebClient
.builder() .builder()
.baseUrl(AppConfig.getPromoManagementUrl()) .baseUrl(promoManagementUrl)
.build() .build()
.get() .get()
.uri("/api/promos") .uri("/api/promos")
...@@ -43,7 +50,7 @@ public class ProductService { ...@@ -43,7 +50,7 @@ public class ProductService {
} }
public Flux<Promotion> getPromotionBySkus(List<String> listOfSkus){ public Flux<Promotion> getPromotionBySkus(List<String> listOfSkus){
return WebClient.create(String.format("%s/api/promos/bulkSearch",AppConfig.getPromoManagementUrl())) return WebClient.create(String.format("%s/api/promos/bulkSearch",promoManagementUrl))
.post() .post()
.bodyValue(listOfSkus) .bodyValue(listOfSkus)
.retrieve() .retrieve()
......
spring.data.mongodb.uri=${MONGOCONNECTION} # UNCOMMENT FOR PRODUCTION DEPLOYMENT
#spring.data.mongodb.uri=${MONGOCONNECTION}
# UNCOMMENT FOR LOCAL TESTING
spring.data.mongodb.uri=mongodb+srv://ecom:ecom@e-commerce-db-cluster.va815.mongodb.net/e-commerce-db?retryWrites=true&w=majority
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
#products.apiUrl=http://localhost:8083
#promos.apiUrl=http://localhost:8082
#orders.apiUrl=http://localhost:8084
\ No newline at end of file
...@@ -4047,7 +4047,8 @@ ...@@ -4047,7 +4047,8 @@
"node_modules/babel-runtime/node_modules/core-js": { "node_modules/babel-runtime/node_modules/core-js": {
"version": "2.6.12", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"hasInstallScript": true
}, },
"node_modules/babel-runtime/node_modules/regenerator-runtime": { "node_modules/babel-runtime/node_modules/regenerator-runtime": {
"version": "0.11.1", "version": "0.11.1",
...@@ -5075,7 +5076,8 @@ ...@@ -5075,7 +5076,8 @@
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.11.2", "version": "3.11.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.11.2.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.11.2.tgz",
"integrity": "sha512-3tfrrO1JpJSYGKnd9LKTBPqgUES/UYiCzMKeqwR1+jF16q4kD1BY2NvqkfuzXwQ6+CIWm55V9cjD7PQd+hijdw==" "integrity": "sha512-3tfrrO1JpJSYGKnd9LKTBPqgUES/UYiCzMKeqwR1+jF16q4kD1BY2NvqkfuzXwQ6+CIWm55V9cjD7PQd+hijdw==",
"hasInstallScript": true
}, },
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.11.2", "version": "3.11.2",
...@@ -5097,7 +5099,8 @@ ...@@ -5097,7 +5099,8 @@
"node_modules/core-js-pure": { "node_modules/core-js-pure": {
"version": "3.11.2", "version": "3.11.2",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.2.tgz", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.2.tgz",
"integrity": "sha512-DQxdEKm+zFsnON7ZGOgUAQXBt1UJJ01tOzN/HgQ7cNf0oEHW1tcBLfCQQd1q6otdLu5gAdvKYxKHAoXGwE/kiQ==" "integrity": "sha512-DQxdEKm+zFsnON7ZGOgUAQXBt1UJJ01tOzN/HgQ7cNf0oEHW1tcBLfCQQd1q6otdLu5gAdvKYxKHAoXGwE/kiQ==",
"hasInstallScript": true
}, },
"node_modules/core-util-is": { "node_modules/core-util-is": {
"version": "1.0.2", "version": "1.0.2",
...@@ -6201,6 +6204,7 @@ ...@@ -6201,6 +6204,7 @@
"version": "2.7.4", "version": "2.7.4",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==",
"hasInstallScript": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
...@@ -18734,6 +18738,7 @@ ...@@ -18734,6 +18738,7 @@
"version": "1.2.13", "version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
...@@ -19179,6 +19184,7 @@ ...@@ -19179,6 +19184,7 @@
"version": "1.2.13", "version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
...@@ -3,6 +3,7 @@ import * as ApiUtil from '../util/cart_api_util'; ...@@ -3,6 +3,7 @@ import * as ApiUtil from '../util/cart_api_util';
export const RECEIVE_USER_CART = "RECEIVE_USER_CART"; export const RECEIVE_USER_CART = "RECEIVE_USER_CART";
export const CLEAR_USER_CART = "CLEAR_USER_CART"; export const CLEAR_USER_CART = "CLEAR_USER_CART";
const receiveUserCart = cart => ({ const receiveUserCart = cart => ({
type: RECEIVE_USER_CART, type: RECEIVE_USER_CART,
cart cart
...@@ -12,6 +13,7 @@ export const clearUserCart = () => ({ ...@@ -12,6 +13,7 @@ export const clearUserCart = () => ({
type: CLEAR_USER_CART type: CLEAR_USER_CART
}) })
export const fetchUserCart = userEmail => dispatch => ApiUtil.fetchUserCart(userEmail) export const fetchUserCart = userEmail => dispatch => ApiUtil.fetchUserCart(userEmail)
.then(cart => dispatch(receiveUserCart(cart))); .then(cart => dispatch(receiveUserCart(cart)));
...@@ -20,3 +22,4 @@ export const updateUserCart = (updatedCart, userEmail) => dispatch => ApiUtil.up ...@@ -20,3 +22,4 @@ export const updateUserCart = (updatedCart, userEmail) => dispatch => ApiUtil.up
export const createUserCart = newCart => dispatch => ApiUtil.createCart(newCart) export const createUserCart = newCart => dispatch => ApiUtil.createCart(newCart)
.then(res => res.json) .then(res => res.json)
import {priceCalcUtil, numItemsCalcUtil} from '../util/cart_processing_util'
export const GET_TOTAL_PRICE = "GET_TOTAL_PRICE"
export const GET_NUM_ITEMS = "GET_NUM_ITEMS"
export const GET_CART_PRODUCTS = "GET_CART_PRODUCTS"
export const getTotalPrice = (totalPrice) => ({
type: GET_TOTAL_PRICE,
payload: totalPrice
})
export const getNumItems = (numItems) => ({
type: GET_NUM_ITEMS,
payload: numItems
})
export const getCartProducts = (cartProducts) => ({
type: GET_CART_PRODUCTS,
payload: cartProducts
})
export const calcTotalPrice = (productList) => {
return (dispatch) => {
return dispatch(getTotalPrice(priceCalcUtil(productList)))
}
}
export const calcNumItems = (productList) => {
return (dispatch) => {
return dispatch(getNumItems(numItemsCalcUtil(productList)))
}
}
export const storeCartItemsInProcessing = (productList) => {
return (dispatch) => {
return dispatch(getCartProducts(productList))
}
}
\ No newline at end of file
...@@ -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 = (e) => { let handleSubmitClick = () => {
console.log("Submit Button Clicked") console.log("Submit Button Clicked")
props.setSubmitButtonActive(1) props.setSubmitButtonActive(1)
...@@ -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] === 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>
......
import React, {useState, useEffect} from 'react' import React, { useState, useEffect } from 'react'
import {useSelector, useDispatch} from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import ShippingAddress from './ShippingAddress.js' import ShippingAddress from './ShippingAddress.js'
import PaymentMethod from './PaymentMethod.js' import PaymentMethod from './PaymentMethod.js'
...@@ -8,33 +8,42 @@ import SubmitOrder from './SubmitOrder.js' ...@@ -8,33 +8,42 @@ 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 { calcTotalPrice, calcNumItems, storeCartItemsInProcessing } from './../../actions/cart_processing_actions'
export default function Checkout() { export default function Checkout() {
///////////////////////
// REDUX Global State const dispatch = useDispatch()
///////////////////////
// const {currentUser} = useSelector(state => state)
// const {cart} = useSelector(state => state)
/////////////////////// ///////////////////////
// Shipping Info State // Shipping Info State
/////////////////////// ///////////////////////
const [firstName, setFirstName] = useState([]) // const [firstName, setFirstName] = useState("Guy")
const [lastName, setLastName] = useState([]) // const [lastName, setLastName] = useState("Fieri")
const [shippingAddress, setShippingAddress] = useState([]) // const [shippingAddress, setShippingAddress] = useState("1234 Flavortown Dr")
const [aptSuiteNo, setAptSuiteNo] = useState([]) // const [aptSuiteNo, setAptSuiteNo] = useState("Apt 317")
const [city, setCity] = useState([]) // const [city, setCity] = useState("Santa Rosa")
const [state, setState] = useState([]) // const [state, setState] = useState("California")
const [zipCode, setZipCode] = useState([]) // const [zipCode, setZipCode] = useState("90210")
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 // Billing Info State
/////////////////////// ///////////////////////
const [cardNumber, setCardNumber] = useState(["123456789"]) const [cardNumber, setCardNumber] = useState(["123456789"])
const [cardHolderName, setCardHolderName] = useState(["Jane Doe"]) const [cardHolderName, setCardHolderName] = useState(["Guy Fieri"])
const [expirationDate, setExpirationDate] = useState(["05/20206"]) const [expirationDate, setExpirationDate] = useState(["05/20206"])
const [cvv, setCVV] = useState(["123"]) const [cvv, setCVV] = useState(["123"])
...@@ -42,170 +51,107 @@ export default function Checkout() { ...@@ -42,170 +51,107 @@ export default function Checkout() {
/////////////////////// ///////////////////////
// Order Review / Summary State // Order Review / Summary State
/////////////////////// ///////////////////////
const [numCartItems, setNumCartItems] = useState([3])
const [itemsTotal, setItemsTotal] = useState([87.65]) // arbitrary cart total const numCartItems = useSelector(state => state.cartProcessing.numItems)
const [shippingHandling, setShippingHandling] = useState([8.00]) // arbitraty shipping and handling amount const itemsTotal = useSelector(state => state.cartProcessing.totalPrice)
const [taxRate, setTaxRate] = useState([0.0725]) // california tax rate const taxRate = 0.0725
const shippingHandling = 9.00
/////////////////////// ///////////////////////
// Submit Button State // Submit Button State
/////////////////////// ///////////////////////
const [submitButtonActive, setSubmitButtonActive] = useState([0]) const [submitButtonActive, setSubmitButtonActive] = useState(0)
const [allFieldsValidated, setAllFieldsValidated] = useState([0]) const [allFieldsValidated, setAllFieldsValidated] = useState(0)
const [errorWhileValidating, setErrorWhileValidating] = useState([0]) const [errorWhileValidating, setErrorWhileValidating] = useState(0)
/////////////////////// ///////////////////////
// Submit Action State // Form Submission State
/////////////////////// ///////////////////////
const user = useSelector(state => state.user.currentUser)
const dispatch = useDispatch() const cart = useSelector(state => state.cart)
useEffect(() => {
if (firstName && lastName && shippingAddress && city && state && zipCode) {
console.log("All Fields Validated!")
setAllFieldsValidated(1)
}
// This effect listens for the submit button to be clicked }, [firstName, lastName, shippingAddress, city, state, zipCode])
// It then checks each of the required fields to make sure they are non null
useEffect( () => { useEffect(() => {
// when submit button clicked after being inactive
if (submitButtonActive === 1) { if (submitButtonActive === 1) {
if (allFieldsValidated) {
// fields to make sure are non-empty setErrorWhileValidating(0)
let fieldsToCheck = [ handleSubmit()
firstName, console.log("Successful submission request")
lastName, setSubmitButtonActive(0)
shippingAddress,
city,
state, const cartUpdateObj = {
zipCode, userId: user.email,
// cardNumber, cartItems: []
// 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 // clear user cart now that order has been placed
dispatch(updateUserCart(cartUpdateObj, cartUpdateObj.userId))
dispatch(storeCartItemsInProcessing([]))
dispatch(calcTotalPrice([]))
dispatch(calcNumItems([]))
}
else { else {
console.log("Failed to validate")
setErrorWhileValidating(1) setErrorWhileValidating(1)
setSubmitButtonActive(0) setSubmitButtonActive(0)
} }
} }
else { ; }
// when submit button gets reset from active to inactive
else if (submitButtonActive === 0) {
;
}
}, [submitButtonActive]) }, [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 = () => { let handleSubmit = () => {
console.log("Submitting Order!")
// let order = {} let orderInfo = {
// 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": { "user": {
"userId": "e-com-test-id", "userId": user.userId,
"email": "test@test.test", "email": user.email,
"firstName":"ecom", "firstName": user.firstName,
"lastName":"test" "lastName": user.lastName,
"accessToken": user.accessToken
}, },
"address": { "address": {
"state": "IL", "state": state,
"city": "chicago", "city": city,
"zip": "90210", "zip": zipCode,
"street": "Grand" "street": shippingAddress
}, },
"cart": { "cart": {
"id": "something", "id": "something",
"userId": "e-com-test-id", "userId": user.userId,
"cartItems":[ "cartItems": cart
{ }
"quantity": 2,
"productRef": {
"id": "something",
"sku": "AFP-1",
"upc": "00002"
}
}
]
}
} }
dispatch(dispatchOrderInfo(chrisSpec)) console.log(orderInfo)
dispatch(dispatchOrderInfo(orderInfo))
} }
//redirect to order confirmation page once reponse is recieved
const {orderResponse} = useSelector(state => state.orderStatus);
if (orderResponse.id) return <Redirect to="/order-confirmation" />
// 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 ( return (
<div id="checkout-container"> <div id="checkout-container">
{/* Collects User's shipping info */} {/* Collects User's shipping info */}
<ShippingAddress <ShippingAddress
captureFirstName={setFirstName} captureFirstName={setFirstName}
captureLastName={setLastName} captureLastName={setLastName}
captureShippingAddress={setShippingAddress} captureShippingAddress={setShippingAddress}
...@@ -220,7 +166,7 @@ export default function Checkout() { ...@@ -220,7 +166,7 @@ export default function Checkout() {
cardNumber={cardNumber} cardNumber={cardNumber}
cardHolderName={cardHolderName} cardHolderName={cardHolderName}
expirationDate={expirationDate} expirationDate={expirationDate}
cvv={cvv} cvv={cvv}
captureCardNumber={setCardNumber} captureCardNumber={setCardNumber}
captureCardHolderName={setCardHolderName} captureCardHolderName={setCardHolderName}
captureExpirationDate={setExpirationDate} captureExpirationDate={setExpirationDate}
...@@ -242,7 +188,7 @@ export default function Checkout() { ...@@ -242,7 +188,7 @@ export default function Checkout() {
errorWhileValidating={errorWhileValidating} errorWhileValidating={errorWhileValidating}
allFieldsValidated={allFieldsValidated} allFieldsValidated={allFieldsValidated}
/> />
</div> </div>
) )
} }
......
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>
......
...@@ -22,9 +22,19 @@ export default function CartItem(props) { ...@@ -22,9 +22,19 @@ export default function CartItem(props) {
<div id="productName">{props.productInfo.productName}</div> <div id="productName">{props.productInfo.productName}</div>
<div id="productStock">{props.productInfo.availableStock} left in stock</div> <div id="productStock">{props.productInfo.availableStock} left in stock</div>
<div id="quantityControls"> <div className="quantityControls">
<select id="productQuantitySelect"><option>{props.productInfo.quantity}</option></select>
<button>Delete</button> <input
className="productQuantitySelect"
type="number"
value={props.quantity}
min="1"
max="99"
onChange={(e) => {props.handleQuantityUpdate(props.productInfo.sku, parseInt( e.target.value) )}}
/>
<button onClick={() => { props.handleDelete(props.productInfo.sku) }}>Delete</button>
</div> </div>
</div> </div>
......
import React, { useState, useEffect } from 'react'
import CartItem from './CartItem.js'
import './shopping-cart.css'
import { useSelector, useDispatch } from 'react-redux'
import { Link, Redirect } from 'react-router-dom'
import { updateUserCart} from './../../actions/cart_actions'
import { calcTotalPrice, calcNumItems, storeCartItemsInProcessing } from './../../actions/cart_processing_actions'
export default function ShoppingCart() {
const dispatch = useDispatch()
const userSession = useSelector(state => state.user.currentUser)
const allProducts = useSelector(state => state.market.products)
const [cartRefs, setCartRefs] = useState(useSelector(state => state.cart))
const [cartItems, setCartItems] = useState([])
////////////////////////////////////////////////////////////////
// Map Product Refs to Products that exist in redux global state
////////////////////////////////////////////////////////////////
useEffect(() => {
const productsFromRefs = []
cartRefs.map((cartRef) => {
productsFromRefs.push((
{
product: allProducts.filter((currProduct) => (currProduct.sku === cartRef.productRef.sku))[0],
quantity: cartRef.quantity
}))
})
console.log("NEW PRODUCTS FROM REFS YO")
console.log(productsFromRefs)
setCartItems(productsFromRefs)
}, [cartRefs])
////////////////////////////////
// Delete Item from Cart
////////////////////////////////
let handleDelete = (skuToBeDeleted) => {
const newCartRefs = cartRefs.filter((cartRef) => !(cartRef.productRef.sku === skuToBeDeleted))
setCartRefs(newCartRefs)
}
////////////////////////////////
// Update Quantity of an item
////////////////////////////////
let handleQuantityUpdate = (skuToBeUpdated, newQuantity) => {
const newCartRefs = []
cartRefs.map( (cartRef) => {
if (cartRef.productRef.sku === skuToBeUpdated) {
let temp = cartRef
temp.quantity = newQuantity
newCartRefs.push(temp)
}
else {
newCartRefs.push(cartRef)
}
})
setCartRefs(newCartRefs)
}
////////////////////////////////////////////////////////////////
// Sync Cart with backend and redux global state
////////////////////////////////////////////////////////////////
useEffect(() => {
if (!userSession) return;
const cartUpdateObj = {
userId: userSession.email,
cartItems: cartRefs
}
dispatch(updateUserCart(cartUpdateObj, cartUpdateObj.userId))
dispatch(storeCartItemsInProcessing(cartItems))
dispatch(calcTotalPrice(cartItems))
dispatch(calcNumItems(cartItems))
}, [cartItems])
//if (!userSession) return <Redirect to="/product-market" />
return (
<div id="shoppingCartContainer">
{/* map each cart item into CartItem component */}
<div id="cartItemList">
{cartItems.map((currItem) => {
return (
<CartItem
productInfo={currItem.product}
quantity={currItem.quantity}
handleDelete={handleDelete}
handleQuantityUpdate={handleQuantityUpdate}
key={currItem.product.sku}
/>
)
})}
</div>
{
cartItems.length > 0 ? (
<Link id="checkoutButtonContainer" to="/checkout">
<button id="checkoutButton">Proceed To Checkout</button>
</Link>
) : (
<div>Add more items to your cart!</div>
)
}
</div>
)
}
class Config { class Config {
static baseApiUrl = "13.93.237.130:8084"; //env file // static baseApiUrl = "http://13.93.237.130:8084"; //env file
static baseApiUrl = "http://localhost:8080"; //env file
static orderHistoryApiUrlMethod = (userId) => `${this.baseApiUrl}/api/orders/byUser/${userId}`; static orderHistoryApiUrlMethod = (userId) => `${this.baseApiUrl}/api/orders/byUser/${userId}`;
......
import {GET_TOTAL_PRICE, GET_NUM_ITEMS, GET_CART_PRODUCTS} from './../actions/cart_processing_actions'
const initialState = {
cartProducts: [],
numItems: 0,
totalPrice: 0
}
const cartProcessingReducer = (prevState = initialState, action) => {
Object.freeze(prevState)
const nextState = {...prevState}
switch (action.type) {
case GET_TOTAL_PRICE:
nextState.totalPrice = action.payload
return nextState
case GET_NUM_ITEMS:
nextState.numItems = action.payload
return nextState
case GET_CART_PRODUCTS:
nextState.cartProducts = action.payload
return nextState
default:
return nextState
}
}
export default cartProcessingReducer
\ No newline at end of file
...@@ -3,14 +3,15 @@ import productsReducer from './products_reducer'; ...@@ -3,14 +3,15 @@ import productsReducer from './products_reducer';
import orderReducer from './order_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';
import cartProcessingReducer from './cart_processing_reducer'
const rootReducer = combineReducers({ const rootReducer = combineReducers({
market: productsReducer, market: productsReducer,
user: userReducer, user: userReducer,
cart: cartReducer, cart: cartReducer,
orderStatus: orderReducer orderStatus: orderReducer,
cartProcessing: cartProcessingReducer
}); });
export default rootReducer; export default rootReducer;
\ No newline at end of file
import axios from 'axios'; import axios from 'axios';
import Config from '../config';
export const fetchUserCart = userEmail => { export const fetchUserCart = userEmail => {
return axios.get(`13.93.237.130:8084/api/carts/${userEmail}`) return axios.get(`${Config.baseApiUrl}/api/carts/${userEmail}`)
} }
export const createCart = newCart => { export const createCart = newCart => {
return axios.post(`13.93.237.130:8084/api/carts`, newCart) return axios.post(`${Config.baseApiUrl}/api/carts`, newCart)
} }
export const updateCart = (updatedCart, userEmail) => { export const updateCart = (updatedCart, userEmail) => {
// debugger return axios.put(`${Config.baseApiUrl}/api/carts/${userEmail}`, updatedCart)
return axios.put(`13.93.237.130:8084/api/carts/${userEmail}`, updatedCart) }
}
\ No newline at end of file
export const priceCalcUtil = (productList) => {
const totalPrice = productList.reduce((acc, curr) => {return parseFloat(acc) + (parseFloat(curr.product.price) * parseFloat(curr.quantity))}, 0)
console.log("PRICE IN CART: " + totalPrice)
return totalPrice
}
export const numItemsCalcUtil = (productList) => {
const totalItems = productList.reduce((acc, curr) => {return parseInt(acc) + parseInt(curr.quantity)}, 0)
console.log("ITEMS IN CART: " + totalItems)
return totalItems
}
import Config from './../config'
import axios from 'axios' import axios from 'axios'
export const sendOrderPost = (order) => { export const sendOrderPost = (order) => {
return axios.post("13.93.237.130:8084/api/orders", order) return axios.post(`${Config.baseApiUrl}/api/orders`, order)
} }
\ No newline at end of file
...@@ -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