Commit 17c39e14 authored by Sumaiyya Burney's avatar Sumaiyya Burney

Merge branch 'dev' into 'AFP-10'

Merge Dev int AFP-10

See merge request !13
parents b125e520 b5efca59
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.
......@@ -11,8 +11,12 @@
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
......
......@@ -8,6 +8,8 @@
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0"
crossorigin="anonymous"
/>
<link async rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@${props.versions.sui}/dist/semantic.min.css" />
<script async src="//cdn.jsdelivr.net/npm/semantic-ui@${props.versions.sui}/dist/semantic.min.js"></script>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
......
......@@ -4,6 +4,7 @@ import Main from "./component/Main";
import AuthRoute from "./component/AuthRoute";
import { Switch } from "react-router";
import Login from "./component/Login";
import 'semantic-ui-css/semantic.min.css'
function App() {
return (
......
......@@ -3,6 +3,27 @@ import axios from 'axios';
export const getAllProducts = async data => {
const res = await axios.get(`${Config.inventoryUrl}`);
// console.log(res.data);
return res.data;
}
export const deleteProduct = async (sku) => {
await axios.delete(`${Config.inventoryUrl}/${sku}`)
.then(() => {
getAllProducts();
});
}
export const getFilteredProducts = async (searchTerm, searchBy) => {
const res = await axios.get(`${Config.inventoryUrl}`);
return res.data.filter(product => {
let productFiltered;
if(searchBy === "name"){
productFiltered = product.productName.toLowerCase();
}else if(searchBy === "sku"){
productFiltered = product.sku.toLowerCase();
}
return productFiltered.includes(searchTerm.toLowerCase());
});
}
\ No newline at end of file
import React, { useState } from "react";
import { Link } from "react-router-dom";
import Search from "./Search";
const Header = () => {
const [show, setShow] = useState(false);
......@@ -38,16 +39,7 @@ const Header = () => {
</Link>
</li>
</ul>
<form className="d-flex">
<input
type="search"
className="form-control me-2"
placeholder="search"
/>
<button className="btn btn-light" type="submit">
GO
</button>
</form>
<Search />
</div>
</div>
</nav>
......
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect } from "react";
import { Redirect, Switch } from "react-router";
import AuthRoute from "./AuthRoute";
import ProductForm from "./ProductForm";
import ProductGrid from "./ProductGrid";
import {getAllProducts} from "../actions/apiRequests.js"
import SearchResults from "./SearchResults";
import { getAllProducts } from "../actions/apiRequests.js";
import PromotionNewFormComponent from "./promotionforms/PromotionNewFormComponent";
import PromotionIndexComponent from "./promo_index/PromotionsIndexComponent";
export default function Main() {
const [productData, setproductData] = useState([]);
......@@ -15,23 +18,33 @@ export default function Main() {
const loadProducts = async (event) => {
const data = await getAllProducts();
setproductData(data);
}
};
return (
<div>
<Switch>
<AuthRoute exact path="/products/new">
<ProductForm />
<ProductForm method="POST" />
</AuthRoute>
<AuthRoute exact path="/promos/new">NEW PROMO</AuthRoute>
<AuthRoute exact path="/products">
<ProductGrid productData={productData} />
<AuthRoute exact path="/products/edit/:productId">
<ProductForm method="PUT" />
</AuthRoute>
<AuthRoute
exact
path="/promos/new"
component={PromotionNewFormComponent}
></AuthRoute>
<AuthRoute exact path="/products" component={ProductGrid}></AuthRoute>
<AuthRoute path="/promos">
<PromotionIndexComponent />
</AuthRoute>
<AuthRoute path="/promos">PROMOS</AuthRoute>
<AuthRoute exact path="/">
<Redirect to="/products" />
</AuthRoute>
<AuthRoute >404 PAGE</AuthRoute>
<AuthRoute path="/searchResults" component={SearchResults} />
<AuthRoute>
<h1>404 Page Not Found</h1>
</AuthRoute>
</Switch>
</div>
);
......
import React from 'react'
import './../styles/Product.css'
import React, { useState } from "react";
import "./../styles/Product.css";
import { Modal, Button, Alert } from "react-bootstrap";
export default function Product({product}) {
export default function Product({ product, handleDelete }) {
const [show, setShow] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const handleClose = () => {
setShow(false);
handleCloseConfirm();
};
const handleCloseDelete = (sku) => {
handleDelete(sku)
handleClose();
}
const handleShow = () => setShow(true);
const handleShowConfirm = () => setShowConfirm(true);
const handleCloseConfirm = () => setShowConfirm(false);
return (
<div>
<div className="img-container">
<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>
<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" onClick={() => handleCloseDelete(product.sku)}>Yes, delete</Button>
</Alert>
</Modal.Footer>
</Modal>
</div>
)
);
}
import React from 'react'
import React, { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router";
import emptyProduct from "../emptProduct";
import Config from '../config';
const emptyForm = {
...emptyProduct,
imageFile: "",
};
ProductForm.defaultProps = {
product: { ...emptyForm },
};
export default function ProductForm(props) {
const { method } = props;
const [form, setForm] = useState({ ...emptyForm });
const [imageMode, setImageMode] = useState("url");
const [errors, setErrors] = useState([]);
const history = useHistory();
const { productId } = useParams("productId");
useEffect(() => {
fetch(`http://localhost:8080/api/products/${productId}/`).then((res) => {
if (res.ok) {
console.log(res);
res.json().then((data) => {
Object.keys(data).forEach((key) => {
if (data[key] === null) {
data[key] = "";
}
});
console.log(data);
setForm({ ...data, availableStock: data.stock });
});
}
});
}, [productId]);
const validate = () => {
setErrors([]);
const errs = [];
console.log(form)
if (form.sku.length < 3) {
errs.push("SKU must be at least 3 characters");
}
if (form.upc.length < 3) {
errs.push("UPC must be at least 3 characters");
}
if (form.productName.length === 0) {
errs.push("Please enter a product name");
}
if (form.brand.length === 0) {
errs.push("Please enter a brand");
}
if (form.category.length === 0) {
errs.push("Please specify a category");
}
if (Number(form.price) <= 0) {
errs.push("Price must be greater than 0");
}
if (Number(form.availableStock) <= 0) {
errs.push("Stock must be greater than 0");
}
setErrors([...errs]);
};
const onSubmit = (e) => {
e.preventDefault();
validate();
if (imageMode === "url") {
delete form.imageFile;
} else {
delete form.prodImgUrl;
}
if (errors.length === 0) {
// console.log(form);
const url =
method === "PUT"
? `${Config.inventoryUrl}/${productId}`
: `${Config.inventoryUrl}`;
fetch(url, {
method,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(form),
})
.then((res) => {
if (res.ok) {
res.json().then((data) => console.log(data));
history.push("/products");
} else {
setErrors([
"The provided SKU was found on an existing object, please try another",
]);
}
})
.catch((err) => {
setErrors([err.message]);
});
}
};
export default function ProductForm({ product }) {
return (
<div>
PRODUCT FORM
<div className="container mt-3">
{errors.length ? (
<ul className="list-group">
{errors.map((error, i) => (
<li key={i} className="list-group-item list-group-item-danger">
{error}
</li>
))}
</ul>
) : null}
<form onSubmit={onSubmit}>
<div className="row mt-3">
<div className="col">
<label htmlFor="productSku" className="form-label">
SKU
</label>
<input
disabled={method === "PUT" ? true : false}
required
name="sku"
type="text"
className="form-control"
id="productSku"
disabled={method === "PUT" ? true : false}
value={form.sku}
onChange={(e) => setForm({ ...form, sku: e.target.value })}
/>
</div>
<div className="col">
<label htmlFor="productUpc" className="form-label">
UPC
</label>
<input
required
name="upc"
type="text"
className="form-control"
id="productUpc"
value={form.upc}
onChange={(e) => setForm({ ...form, upc: e.target.value })}
/>
</div>
</div>
<div className="row mt-3">
<div className="col-6">
<div>
<label htmlFor="productName" className="form-label">
Name
</label>
<input
required
name="productName"
type="text"
className="form-control"
id="productName"
value={form.productName}
onChange={(e) =>
setForm({ ...form, productName: e.target.value })
}
/>
</div>
<div>
<label htmlFor="brand" className="form-label">
Brand
</label>
<input
required
name="brand"
type="text"
className="form-control"
id="brand"
value={form.brand}
onChange={(e) => setForm({ ...form, brand: e.target.value })}
/>
</div>
<div>
<label htmlFor="category" className="form-label">
Category
</label>
<input
required
name="category"
type="text"
className="form-control"
id="category"
value={form.category}
onChange={(e) => setForm({ ...form, category: e.target.value })}
/>
</div>
</div>
<div className="col-6">
<label htmlFor="productDesc" className="form-label">
Description
</label>
<textarea
name="productDesciption"
id="productDesc"
cols="40"
rows="7"
className="form-control"
value={form.productDescription}
onChange={(e) =>
setForm({ ...form, productDescription: e.target.value })
}
></textarea>
</div>
</div>
<div className="row mt-3">
<div>
<label htmlFor="price" className="form-label">
Price
</label>
<input
required
name="price"
type="number"
className="form-control"
id="price"
value={form.price}
onChange={(e) => setForm({ ...form, price: e.target.value })}
/>
</div>
<div>
<label htmlFor="stock" className="form-label">
Stock
</label>
<input
disabled={method === "PUT" ? true : false}
required
name="availableStock"
type="number"
className="form-control"
id="stock"
value={form.availableStock}
onChange={(e) => setForm({ ...form, availableStock: e.target.value })}
/>
</div>
</div>
{imageMode === "upload" ? (
<div className="row mt-3">
<div>
<label htmlFor="productImage" className="form-label">
Image File
</label>
<input
id="productImage"
name="imageFile"
className="form-control form-control-lg"
type="file"
value={form.imageFile}
onChange={(e) =>
setForm({ ...form, imageFile: e.target.value })
}
></input>
<button
type="button"
className="btn btn-outline-primary mt-3"
onClick={() => {
setForm({ ...form, imageFile: "" });
setImageMode("url");
}}
>
Specify Image URL
</button>
</div>
</div>
) : null}
{imageMode === "url" ? (
<div className="row mt-3">
<div>
<label htmlFor="prodImgUrl" className="form-label">
Image URL
</label>
<input
name="productImageUrl"
type="text"
className="form-control"
id="prodImgUrl"
value={form.productImageUrl}
onChange={(e) =>
setForm({ ...form, productImageUrl: e.target.value })
}
/>
<button
type="button"
className="btn btn-outline-primary mt-3"
onClick={() => setImageMode("upload")}
>
Upload Image
</button>
</div>
</div>
) : null}
<div className="row mt-3">
<div className="col-12">
<button type="submit" className="btn btn-primary mt-3">
Submit
</button>
</div>
</div>
</form>
</div>
)
);
}
import React from "react";
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";
import { deleteProduct } from "../actions/apiRequests";
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]);
}
};
const handleDelete = (sku) => {
deleteProduct(sku)
.then(() => {
fetchProducts();
});
}
useEffect(() => fetchProducts(), []);
return (
<div>
<h1 id="title" className="text-center" >Inventory</h1>
<Container id="prod-grid" className="mt-3">
<div className="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}>
{productData.map((product) => {
{products.map((product) => {
return (
<Col key={product.sku}>
<Product product={product} />
<Product product={product} handleDelete={() => handleDelete(product.sku)} />
</Col>
);
})}
......
import React from 'react';
import { withRouter, Link } from "react-router-dom";
import { getFilteredProducts } from '../actions/apiRequests';
import "./../styles/Search.css";
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
searchTerm: '',
searchBy: 'name',
results: [],
};
this.submit = this.submit.bind(this);
this.changeTerm = this.changeTerm.bind(this);
this.handleRadioButton = this.handleRadioButton.bind(this);
}
changeTerm(event) {
this.setState({searchTerm: event.target.value});
}
handleRadioButton(value) {
this.setState({
searchBy: value
});
}
async submit(event){
event.preventDefault();
const data = await getFilteredProducts(this.state.searchTerm, this.state.searchBy)
.then(res => {
this.setState({results: res});
})
.catch(err => console.log(err));
if(this.props.location.pathname === "/searchResults"){
this.props.history.push("/products", this.state);
}
this.props.history.push("/searchResults", this.state);
}
//Need to search by name or SKU
render(){
return (
<div>
<form className="d-flex" onSubmit={this.submit}>
<input
type="search"
className="form-control me-2"
placeholder="Search for item..."
onChange={event => this.changeTerm(event)}
/>
<div className="form-check form-check-inline">
<input className="form-check-input" type="radio" name="inlineRadioOptions" checked={this.state.searchBy === "name"} onChange={() => this.handleRadioButton("name")} id="searchByName" value="name" />
<label className="form-check-label" htmlFor="searchByName">Name</label>
</div>
<div className="form-check form-check-inline">
<input className="form-check-input" type="radio" name="inlineRadioOptions" checked={this.state.searchBy === "sku"} onChange={() => this.handleRadioButton("sku")} id="searchBySku" value="sku" />
<label className="form-check-label" htmlFor="searchBySKU">Sku</label>
</div>
<button className="btn btn-light" type="submit">
GO
</button>
</form>
<Link to={{
pathname: '/searchResults',
state: {results: this.state.results}
}} />
</div>
);
}
}
export default withRouter(Search);
\ No newline at end of file
import React from "react";
import Product from "./Product.jsx";
import { Col, Container, Row } from "react-bootstrap";
import "./../styles/ProductGrid.css";
export default class SearchResults extends React.Component{
constructor(props){
super(props);
this.state = {
results: this.props.history.location.state.results
}
}
render(){
return (
<div>
<h1 id="title" className="text-center" >Search Results</h1>
<Container id="prod-grid" className="mt-3">
<Row xs={1} sm={2} md={3} lg={4}>
{this.state.results.length > 0 ? this.state.results.map((product) => {
return (
<Col key={product.sku}>
<Product product={product} />
</Col>
);
}) :
<Col>
<p>Unable to find any matching products.</p>
</Col>
}
</Row>
</Container>
</div>
);
}
}
\ No newline at end of file
import {useState, useEffect} from 'react';
import Config from '../../config';
import "./promolistStyle.css";
import { NavLink } from 'react-router-dom';
import { getAllProducts } from "../../actions/apiRequests.js";
export default function PromotionIndexComponent () {
const [promoData, setPromoData] = useState([]);
useEffect( () => {
loadPromotions();
}, [])
const loadPromotions = async () => {
const response = await fetch(`${Config.promotionsUrl}`);
const data = await response.json();
setPromoData(data);
}
const deletePromotion = (promoId) => {
fetch(`${Config.promotionsUrl}/${promoId}`, {
method: "DELETE",
})
.then(res => loadPromotions())
.catch(err => console.log("error"))
}
return (
<div>
<div className="promo-container">
<div className="promo-list-container">
<div className="promo-header">
<h1 className="promo-title"> Promotions</h1>
<NavLink className="add-navLink" to='/promos/new'><button className="btn-success">+ New Promotion</button></NavLink>
</div>
<table className="promo-index-list">
<thead className="promo-table-title">
<tr>
<th>
Promotion Id
</th>
<th>
Product SKU
</th>
<th>
Discount
</th>
<th>
Minimum Quanitity
</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
{promoData.map((promo, key) => {
return (
<tr key={key}>
<td>
{promo.promotionId}
</td>
<td>
{promo.productSku}
</td>
<td>
{promo.discountPercentage}%
</td>
<td>
{promo.minimumQuantity}
</td>
<td>
<NavLink className="edit-navLink" to={{
pathname: `/promos/${promo.promotionId}/update`,
state: {promo}
}}>
<button className="btn-primary">Update</button>
</NavLink>
</td>
<td>
<button className="btn-danger btn-sm" onClick={() => deletePromotion(promo.promotionId)}>Delete</button>
</td>
</tr>
)
}).reverse()
}
</tbody>
</table>
</div>
</div>
</div>
)
}
.promo-container {
margin: 30px auto;
padding: 0 16px;
}
.promo-header {
background-color: #212529;
display: flex;
justify-content: space-between;
padding-left: 10px;
padding-right: 10px;
}
.promo-title {
color: white;
padding: 8px;
}
.add-navLink {
align-self: center;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
text-align: left;
padding: 8px;
}
tr td {
height: 40px;
}
tr {
box-sizing: border-box;
}
tr:nth-child(even) {
background-color: #dddddd;
}
thead th {
background-color: #dddddd;
height: 35px;
}
tr:hover {
background-color: lightblue;
}
import { useState, useEffect } 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'
import { Dropdown } from 'semantic-ui-react'
import {getAllProducts} from '../../actions/apiRequests'
export default function PromotionNewFormComponent (props) {
const { register, handleSubmit, setValue, formState: { errors } } = useForm();
const history = useHistory();
const [serverErrors, setErrors] = useState([]);
const [productData, setproductData] = 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]))
};
useEffect(() => {
loadProducts();
}, [])
const loadProducts = async (event) => {
const data = await getAllProducts();
setproductData(data);
}
const onChange = (e, data) => {
console.log(data)
setValue('productSku', data.value)
console.log(productSku)
}
const productSku = register('productSku', { required: true })
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"
/> */}
<Dropdown
placeholder='Select Product'
fluid
search
selection
onChange={onChange}
onSearchChange={onChange}
options={productData.map(prod => {
return {
key: prod.sku,
text: prod.sku,
value: prod.sku,
}
})}
/>
{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>
);
}
import { useState, useEffect } 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'
import { Dropdown } from 'semantic-ui-react'
import { getAllProducts } from '../../actions/apiRequests'
export default function PromotionUpdateFormComponent(props) {
const { register, handleSubmit, setValue, formState: { errors } } = useForm();
const history = useHistory();
const [serverErrors, setErrors] = useState([]);
const [productData, setproductData] = useState([]);
const onSubmit = (data) => {
console.log(data)
fetch(`${Config.promotionsUrl}/${data.promotionId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then(res => res.ok ? history.push("/promos") : setErrors(["error"]))
.catch(err => setErrors([err.message]))
};
useEffect( () => {
loadProducts();
}, [])
const loadProducts = async (event) => {
const data = await getAllProducts();
setproductData(data);
}
const onChange = (e, data) => {
setValue('productSku', data.value)
}
const productSku = register('productSku', { required: true })
return (
<div>
<div className="promo-container">
<div className="promo-form-container">
<h1 className="promo-form-title">Update 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", {
value: props.location.state.promo.promotionId,
required: "promotion id is required",
maxLength: { value: 10, message: "You exceeded the maximum value" },
})}
readOnly
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>
<Dropdown
placeholder='Select Product'
fluid
search
selection
defaultValue={props.location.state.promo.productSku}
onChange={onChange}
onSearchChange={onChange}
options={productData.map(prod => {
return {
key: prod.sku,
text: prod.sku,
value: prod.sku,
}
})} />
{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,
value: props.location.state.promo.discountPercentage,
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={props.location.state.promo.discountPercentage }
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,
value: props.location.state.promo.minimumQuantity,
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={props.location.state.promo.minimumQuantity}
/>
{errors.minimumQuantity && <p className="form-error">{errors.minimumQuantity.message}</p>}
</div>
<input className="promo-submit" type="submit" value="Update 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
......@@ -3,4 +3,4 @@ class Config {
static promotionsUrl = "http://localhost:8081/api/promos";
}
export default Config;
\ No newline at end of file
export default Config;
const emptyProduct = {
sku: "",
upc: "",
productName: "",
brand: "",
category: "",
price: 0.0,
availableStock: 0,
productDesciption: "",
productImageUrl: "",
};
export default emptyProduct;
img {
max-width: 100%;
max-height: 250px;
}
.img-container{
position:relative;
overflow:hidden;
padding-bottom:100%;
}
img {
.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
.form-check-label{
color: rgba(255, 255, 255, 0.55)
}
.form-check.form-check-inline{
margin-top: 0.35rem;
}
\ 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