Commit 126ba0b9 authored by John Lam's avatar John Lam

Merge branch 'dev' into 'AFP-14'

# Conflicts:
#   src/component/Main.jsx
#   src/component/ProductForm.jsx
parents fcd0852e 775cec94
...@@ -24,3 +24,4 @@ yarn-error.log* ...@@ -24,3 +24,4 @@ yarn-error.log*
.env .env
yarn.lock
FROM node:14.16-alpine3.13 as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY . .
RUN yarn
RUN yarn build
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY --from=build /app/nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
}
This diff is collapsed.
...@@ -6,8 +6,13 @@ ...@@ -6,8 +6,13 @@
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",
"axios": "^0.21.1",
"bootstrap": "^5.0.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
......
import Config from '../config';
import axios from 'axios';
export const getAllProducts = async data => {
const res = await axios.get(`${Config.inventoryUrl}`);
// console.log(res.data);
return res.data;
}
\ No newline at end of file
import React from "react"; import React, { useState, useEffect } from "react";
import { Redirect, Switch } from "react-router"; import { Redirect, Switch } from "react-router";
import AuthRoute from "./AuthRoute"; import AuthRoute from "./AuthRoute";
import ProductForm from "./ProductForm"; import ProductForm from "./ProductForm";
import ProductGrid from "./ProductGrid";
import { getAllProducts } from "../actions/apiRequests.js";
import PromotionNewFormComponent from "./promotionforms/PromotionNewFormComponent";
export default function Main() { export default function Main() {
const [productData, setproductData] = useState([]);
useEffect(() => {
loadProducts();
}, []);
const loadProducts = async (event) => {
const data = await getAllProducts();
setproductData(data);
};
return ( return (
<div> <div>
<Switch> <Switch>
<AuthRoute exact path="/products/new"> <AuthRoute exact path="/products/new">
<ProductForm method="POST" /> <ProductForm method="POST" />
</AuthRoute> </AuthRoute>
<AuthRoute path="/products/edit/:productId"> <AuthRoute exact path="/products/edit/:sku">
<ProductForm method="PUT" /> <ProductForm method="PUT" />
</AuthRoute> </AuthRoute>
<AuthRoute exact path="/promos/new"> <AuthRoute
NEW PROMO exact
</AuthRoute> path="/promos/new"
<AuthRoute exact path="/products"> component={PromotionNewFormComponent}
PRODUCTS ></AuthRoute>
</AuthRoute> <AuthRoute exact path="/products" component={ProductGrid}></AuthRoute>
<AuthRoute path="/promos">PROMOS</AuthRoute> <AuthRoute path="/promos">PROMOS</AuthRoute>
<AuthRoute exact path="/"> <AuthRoute exact path="/">
<Redirect to="/products" /> <Redirect to="/products" />
</AuthRoute> </AuthRoute>
<AuthRoute>404 PAGE</AuthRoute> <AuthRoute>
<h1>404 Page Not Found</h1>
</AuthRoute>
</Switch> </Switch>
</div> </div>
); );
......
import React, { useState } from "react";
import "./../styles/Product.css";
import { Modal, Button, Alert } from "react-bootstrap";
export default function Product({ product }) {
const [show, setShow] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const handleClose = () => {
setShow(false);
handleCloseConfirm();
};
const handleShow = () => setShow(true);
const handleShowConfirm = () => setShowConfirm(true);
const handleCloseConfirm = () => setShowConfirm(false);
return (
<div>
<div className="img-container" onClick={handleShow}>
<img
className="grid-img"
src={product.productImageUrl}
alt={product.productName}
/>
</div>
<div className="prod-info">
<h5>{product.productName}</h5>
{product.sku}
<br />${product.price}
<br />
In Stock: {product.stock}
</div>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>{product.productName}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="modal-img">
<img src={product.productImageUrl} alt={product.productName} />
</div>
<h5>{product.sku}</h5>${product.price}
<br />
{product.productDescription}
<br />
In Stock: {product.stock}
</Modal.Body>
<Modal.Footer>
<Button
variant="danger"
className="float-left"
onClick={handleShowConfirm}
>
Delete product
</Button>
<Button variant="primary" href={`/products/edit/${product.sku}`}>
Edit Product
</Button>
<Alert show={showConfirm} variant="danger">
<h5> Are you sure?</h5>
<Button variant="secondary" onClick={handleCloseConfirm}>
Cancel
</Button>
&nbsp;&nbsp;
<Button variant="danger">Yes, delete</Button>
</Alert>
</Modal.Footer>
</Modal>
</div>
);
}
...@@ -78,8 +78,8 @@ export default function ProductForm(props) { ...@@ -78,8 +78,8 @@ export default function ProductForm(props) {
// console.log(form); // console.log(form);
const url = const url =
method === "PUT" method === "PUT"
? `http://localhost:8080/api/products/${productId}` ? `${Config.inventoryUrl}/${sku}`
: "http://localhost:8080/api/products"; : `${Config.inventoryUrl}`;
fetch(url, { fetch(url, {
method, method,
headers: { headers: {
...@@ -121,6 +121,7 @@ export default function ProductForm(props) { ...@@ -121,6 +121,7 @@ export default function ProductForm(props) {
SKU SKU
</label> </label>
<input <input
disabled={method === "PUT" ? true : false}
required required
name="sku" name="sku"
type="text" type="text"
...@@ -230,6 +231,7 @@ export default function ProductForm(props) { ...@@ -230,6 +231,7 @@ export default function ProductForm(props) {
Stock Stock
</label> </label>
<input <input
disabled={method === "PUT" ? true : false}
required required
name="availableStock" name="availableStock"
type="number" type="number"
......
import React, { useEffect, useState } from "react";
import Product from "./Product.jsx";
import { Col, Container, Row } from "react-bootstrap";
import "./../styles/ProductGrid.css";
import Config from "../config.js";
import { Link } from "react-router-dom";
export default function ProductGrid({ productData }) {
const [products, setProducts] = useState([]);
const fetchProducts = async () => {
const res = await fetch(`${Config.inventoryUrl}`);
if (res.ok) {
const data = await res.json();
setProducts([...data]);
}
};
useEffect(() => fetchProducts(), []);
return (
<div class="container flex-column d-flex justify-content-center">
<div className="container mt-3 d-flex justify-content-between align-items-center">
<h1 id="title" className="text-center">
Inventory
</h1>
<Link type="link" className="btn btn-success" to="/products/new">
+ New Product
</Link>
</div>
<Container id="prod-grid" className="mt-3 mx-auto">
<Row xs={1} sm={2} md={3} lg={4}>
{products.map((product) => {
return (
<Col key={product.sku}>
<Product product={product} />
</Col>
);
})}
</Row>
</Container>
</div>
//uses vanilla bootstrap
// <div>
// <h1 id="title">Inventory</h1>
// <div className="container" id="prod-grid" >
// <div className="row row-cols-4">
// {productData.map((product) => {
// return (
// <div className="col" key={product.sku}>
// <Product product={product}/>
// </div>
// )
// })}
// </div>
// </div>
// </div>
);
}
import { useState } from "react";
import { useForm } from "react-hook-form";
import "./promoStyle.css";
import { NavLink } from 'react-router-dom';
import Config from '../../config';
import { useHistory } from 'react-router'
export default function PromotionNewFormComponent () {
const { register, handleSubmit, formState: { errors } } = useForm();
const history = useHistory();
const [serverErrors, setErrors] = useState([]);
const onSubmit = (data) => {
console.log(data)
fetch(`${Config.promotionsUrl}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then(res => res.ok ? history.push("/promos") : setErrors(["There is already an existing Promotion Id"]))
.catch(err => setErrors([err.message]))
};
return (
<div>
<div className="promo-container">
<div className="promo-form-container">
<h1 className="promo-form-title">Add Promotion</h1>
<form className="promo-form" onSubmit={handleSubmit(onSubmit)}>
{serverErrors.length ? <p className="form-error">{serverErrors}</p> : ""}
<div className="form-group">
<label>Promotion Id</label>
<input
{...register("promotionId", {
required: "promotion id is required",
maxLength: { value: 10, message: "You exceeded the maximum value" }
})}
id="promotionId"
className="form-control"
placeholder="eg. PROMO-403"
/>
{errors.promotionId && <p className="form-error">{errors.promotionId.message}</p>}
</div>
<div className="form-group">
<label>Product Sku</label>
<input
{...register("productSku", {
required: "product sku required",
maxLength: { value: 10, message: "You exceeded the maximum value" }
})}
id="productSku"
className="form-control"
placeholder="eg. SKU-39SD"
/>
{errors.productSku && <p className="form-error">{errors.productSku.message}</p>}
<br></br>
</div>
<div className="form-group">
<label>Enter % off</label>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text">%</span></div>
<input
{...register("discountPercentage", {
valueAsNumber: true,
required: "% off required",
min: { value: 0, message: "% must be greater than or equal to 0" },
max: { value: 100, message: "% must be less than or equal to 100" },
})}
id="discountPercentage"
className="form-control"
placeholder="20"
type="number"
/>
</div>
{errors.discountPercentage && <p className="form-error">{errors.discountPercentage.message}</p>}
</div>
<div className="form-group">
<label>Applies at Minimum Quantity</label>
<input
{...register("minimumQuantity", {
valueAsNumber: true,
required: "minimum quantity is required",
min: { value: 1, message: "discount percentage must be greater than 0" },
})}
id="minimumQuantity"
className="form-control"
type="number"
min="1"
placeholder="1"
/>
{errors.minimumQuantity && <p className="form-error">{errors.minimumQuantity.message}</p>}
</div>
<input className="promo-submit" type="submit" value="Save Promotion" />
<NavLink className="promo-cancel" to="/promos">Cancel</NavLink>
</form>
</div>
</div>
</div>
);
}
.promo-container {
margin: 0px auto;
padding: 50px;
border-radius: 3px;
}
.promo-form-title {
text-align: center;
background-color: #212529;
color: white;
padding: 8px;
}
.promo-form-container {
border: 1px solid #212529;
}
.promo-form {
padding: 50px;
}
.form-group {
padding: 5px;
}
.promo-submit {
background-color: #212529;
border: 1px solid black;
border-radius: 4px;
color: white;
min-width: 180px;
height: 45px;
text-align: center;
margin-left: 5px;
margin-top: 15px;
}
.promo-submit:hover {
background-color: #212529;
filter: brightness(90%);
}
.promo-cancel {
margin-left: 15px;
}
.form-error {
color: red;
}
\ No newline at end of file
class Config {
static inventoryUrl = "http://localhost:8080/api/products";
static promotionsUrl = "http://localhost:8081/api/promos";
}
export default Config;
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import 'bootstrap/dist/css/bootstrap.css';
import "./index.css"; import "./index.css";
import App from "./App"; import App from "./App";
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from "./reportWebVitals";
......
img {
max-width: 100%;
max-height: 250px;
}
.img-container{
position:relative;
overflow:hidden;
padding-bottom:100%;
}
.grid-img {
position: absolute;
max-width: 100%;
max-height: 100%;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.modal-img {
display: flex;
justify-content: center;
}
.alert{
width: 100%;
}
\ No newline at end of file
#title {
margin-left: 1em;
}
#prod-grid {
margin-left: 2em;
}
\ No newline at end of file
This diff is collapsed.
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