Commit fc83aa29 authored by John Lam's avatar John Lam

Merge branch 'dev' into 'master'

Dev

See merge request !27
parents b1bb50d8 19d97ad5
...@@ -21,3 +21,7 @@ ...@@ -21,3 +21,7 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.env
yarn.lock
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/inventory-promotion-react.iml" filepath="$PROJECT_DIR$/.idea/inventory-promotion-react.iml" />
</modules>
</component>
</project>
\ No newline at end of file
Index: package-lock.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json (revision 7d32de398b464649bcb5423b66d848f7da2d3f88)
+++ b/package-lock.json (date 1620844182946)
@@ -16,6 +16,7 @@
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2",
+ "react-google-login": "^5.2.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
@@ -14626,6 +14627,19 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
+ "node_modules/react-google-login": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.2.2.tgz",
+ "integrity": "sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==",
+ "dependencies": {
+ "@types/react": "*",
+ "prop-types": "^15.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16 || ^17",
+ "react-dom": "^16 || ^17"
+ }
+ },
"node_modules/react-hook-form": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.4.1.tgz",
@@ -31867,6 +31881,15 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
+ "react-google-login": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.2.2.tgz",
+ "integrity": "sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==",
+ "requires": {
+ "@types/react": "*",
+ "prop-types": "^15.6.0"
+ }
+ },
"react-hook-form": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.4.1.tgz",
Index: package.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>{\n \"name\": \"inventory-promotions-frontend\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"@testing-library/jest-dom\": \"^5.11.4\",\n \"@testing-library/react\": \"^11.1.0\",\n \"@testing-library/user-event\": \"^12.1.10\",\n \"axios\": \"^0.21.1\",\n \"bootstrap\": \"^5.0.0\",\n \"react\": \"^17.0.2\",\n \"react-bootstrap\": \"^1.5.2\",\n \"react-dom\": \"^17.0.2\",\n \"react-hook-form\": \"^7.4.1\",\n \"react-router\": \"^5.2.0\",\n \"react-router-dom\": \"^5.2.0\",\n \"react-scripts\": \"4.0.3\",\n \"semantic-ui-css\": \"^2.4.1\",\n \"semantic-ui-react\": \"^2.0.3\",\n \"web-vitals\": \"^1.0.1\"\n },\n \"scripts\": {\n \"start\": \"react-scripts start\",\n \"build\": \"react-scripts build\",\n \"test\": \"react-scripts test\",\n \"eject\": \"react-scripts eject\"\n },\n \"eslintConfig\": {\n \"extends\": [\n \"react-app\",\n \"react-app/jest\"\n ]\n },\n \"browserslist\": {\n \"production\": [\n \">0.2%\",\n \"not dead\",\n \"not op_mini all\"\n ],\n \"development\": [\n \"last 1 chrome version\",\n \"last 1 firefox version\",\n \"last 1 safari version\"\n ]\n }\n}\n
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/package.json b/package.json
--- a/package.json (revision 7d32de398b464649bcb5423b66d848f7da2d3f88)
+++ b/package.json (date 1620844182946)
@@ -11,6 +11,7 @@
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2",
+ "react-google-login": "^5.2.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
<changelist name="Uncommitted_changes_before_Checkout_at_5_12_21,_11_41_AM_[Default_Changelist]" date="1620844900240" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Checkout_at_5_12_21,_11_41_AM_[Default_Changelist]/shelved.patch" />
<option name="DESCRIPTION" value="Uncommitted changes before Checkout at 5/12/21, 11:41 AM [Default Changelist]" />
</changelist>
\ No newline at end of file
Index: package-lock.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json (revision 7d32de398b464649bcb5423b66d848f7da2d3f88)
+++ b/package-lock.json (date 1620844900145)
@@ -16,6 +16,7 @@
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2",
+ "react-google-login": "^5.2.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
@@ -14626,6 +14627,19 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
+ "node_modules/react-google-login": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.2.2.tgz",
+ "integrity": "sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==",
+ "dependencies": {
+ "@types/react": "*",
+ "prop-types": "^15.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16 || ^17",
+ "react-dom": "^16 || ^17"
+ }
+ },
"node_modules/react-hook-form": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.4.1.tgz",
@@ -31867,6 +31881,15 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
+ "react-google-login": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/react-google-login/-/react-google-login-5.2.2.tgz",
+ "integrity": "sha512-JUngfvaSMcOuV0lFff7+SzJ2qviuNMQdqlsDJkUM145xkGPVIfqWXq9Ui+2Dr6jdJWH5KYdynz9+4CzKjI5u6g==",
+ "requires": {
+ "@types/react": "*",
+ "prop-types": "^15.6.0"
+ }
+ },
"react-hook-form": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.4.1.tgz",
Index: package.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>{\n \"name\": \"inventory-promotions-frontend\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"@testing-library/jest-dom\": \"^5.11.4\",\n \"@testing-library/react\": \"^11.1.0\",\n \"@testing-library/user-event\": \"^12.1.10\",\n \"axios\": \"^0.21.1\",\n \"bootstrap\": \"^5.0.0\",\n \"react\": \"^17.0.2\",\n \"react-bootstrap\": \"^1.5.2\",\n \"react-dom\": \"^17.0.2\",\n \"react-hook-form\": \"^7.4.1\",\n \"react-router\": \"^5.2.0\",\n \"react-router-dom\": \"^5.2.0\",\n \"react-scripts\": \"4.0.3\",\n \"semantic-ui-css\": \"^2.4.1\",\n \"semantic-ui-react\": \"^2.0.3\",\n \"web-vitals\": \"^1.0.1\"\n },\n \"scripts\": {\n \"start\": \"react-scripts start\",\n \"build\": \"react-scripts build\",\n \"test\": \"react-scripts test\",\n \"eject\": \"react-scripts eject\"\n },\n \"eslintConfig\": {\n \"extends\": [\n \"react-app\",\n \"react-app/jest\"\n ]\n },\n \"browserslist\": {\n \"production\": [\n \">0.2%\",\n \"not dead\",\n \"not op_mini all\"\n ],\n \"development\": [\n \"last 1 chrome version\",\n \"last 1 firefox version\",\n \"last 1 safari version\"\n ]\n }\n}\n
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/package.json b/package.json
--- a/package.json (revision 7d32de398b464649bcb5423b66d848f7da2d3f88)
+++ b/package.json (date 1620844900149)
@@ -11,6 +11,7 @@
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2",
+ "react-google-login": "^5.2.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
<changelist name="Uncommitted_changes_before_Update_at_5_12_21,_11_42_AM_[Default_Changelist]" date="1620844923266" recycled="false" toDelete="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/Uncommitted_changes_before_Update_at_5_12_21,_11_42_AM_[Default_Changelist]/shelved.patch" />
<option name="DESCRIPTION" value="Uncommitted changes before Update at 5/12/21, 11:42 AM [Default Changelist]" />
</changelist>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="782be151-5ffb-4bb8-9cbe-39bce549aa91" name="Default Changelist" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="AFP-6" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="1s6VjxITkLhRTITrUtKzJtqAnzS" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="782be151-5ffb-4bb8-9cbe-39bce549aa91" name="Default Changelist" comment="" />
<created>1620193165194</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1620193165194</updated>
</task>
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="AFP-6" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
<option name="oldMeFiltersMigrated" value="true" />
</component>
</project>
\ No newline at end of file
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,9 +6,18 @@ ...@@ -6,9 +6,18 @@
"@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-google-login": "^5.2.2",
"react-hook-form": "^7.4.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
}, },
"scripts": { "scripts": {
......
...@@ -2,6 +2,13 @@ ...@@ -2,6 +2,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0"
crossorigin="anonymous"
/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
......
import logo from './logo.svg'; import "./App.css";
import './App.css'; import Header from "./component/Header";
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";
import React, { useState, createContext, useEffect } from "react";
export const AuthContext = createContext();
function App() { function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState(null);
console.log(user);
const state = {
user,
setUser,
isLoggedIn,
setIsLoggedIn,
};
useEffect(() => {
localStorage.setItem("loggedIn", isLoggedIn);
}, [isLoggedIn]);
return ( return (
<div className="App"> <AuthContext.Provider value={state}>
<header className="App-header"> <div>
<img src={logo} className="App-logo" alt="logo" /> <Switch>
<p> <AuthRoute exact path="/login">
Edit <code>src/App.js</code> and save to reload. <Login />
</p> </AuthRoute>
<a <AuthRoute>
className="App-link" <Header />
href="https://reactjs.org" <Main />
target="_blank" </AuthRoute>
rel="noopener noreferrer" </Switch>
> </div>
Learn React </AuthContext.Provider>
</a>
</header>
</div>
); );
} }
......
import Config from '../config';
import axios from 'axios';
export const getAllProducts = async data => {
const res = await axios.get(`${Config.inventoryUrl}`);
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 { useEffect, useContext } from "react";
import React from "react";
import { Redirect, Route } from "react-router";
import { AuthContext } from "../App";
export default function AuthRoute({ children, ...rest }) {
const { isLoggedIn } = useContext(AuthContext);
return (
<Route
{...rest}
render={({ location }) => {
if (isLoggedIn) {
if (location.pathname === "/login") {
return <Redirect to="/products" />;
} else return children;
} else {
if (location.pathname !== "/login") {
return <Redirect to="/login" />;
} else return children;
}
}}
/>
);
}
import React, { useState } from "react";
import { Link } from "react-router-dom";
import Search from "./Search";
import ".././styles/Header.css"
import LogoutButton from "./session/LogoutButton";
const Header = ({ login, setLogin }) => {
const [show, setShow] = useState(false);
return (
<nav className="navbar navbar-expand-lg navbar-dark">
<div id="parent" className="container-fluid">
<Link className="navbar-brand" to="/">
<img id="logo" src="../../../logo.jpeg" alt="Nisum Logo"></img>
</Link>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
onClick={() => setShow((prev) => !prev)}
>
<span className="navbar-toggler-icon"></span>
</button>
<div
className={`collapse navbar-collapse ${show ? "show" : ""}`}
id="navbarSupportedContent"
>
<ul className="navbar-nav me-auto mb-2 mb-lg-0">
<li className="nav-item">
<Link className="nav-link" to="/products">
Products
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/promos">
Promotions
</Link>
</li>
</ul>
<LogoutButton />
</div>
</div>
<h1 id="page-title">Inventory, Promotions, Pricing</h1>
</nav>
);
};
export default Header;
import React from 'react'
import LoginButton from './session/LoginButton'
export default function Login({}) {
return (
<div>
<LoginButton />
</div>
)
}
import { Redirect, Switch } from "react-router";
import AuthRoute from "./AuthRoute";
import ProductForm from "./ProductForm";
import ProductIndex from "./ProductIndex";
import SearchResults from "./SearchResults";
import PromotionNewFormComponent from "./promotionforms/PromotionNewFormComponent";
import PromotionIndexComponent from "./promo_index/PromotionsIndexComponent";
import PromotionUpdateFormComponent from "./promotionforms/UpdatePromotionForm";
export default function Main() {
return (
<div>
<Switch>
<AuthRoute exact path="/products/new">
<ProductForm method="POST" />
</AuthRoute>
<AuthRoute exact path="/products/:productId/update">
<ProductForm method="PUT" />
</AuthRoute>
<AuthRoute
exact
path="/promos/new"
component={PromotionNewFormComponent}
></AuthRoute>
<AuthRoute exact path="/promos/:promoId/update" component={PromotionUpdateFormComponent}></AuthRoute>
<AuthRoute exact path="/products" component={ProductIndex}></AuthRoute>
<AuthRoute path="/promos">
<PromotionIndexComponent />
</AuthRoute>
<AuthRoute exact path="/">
<Redirect to="/products" />
</AuthRoute>
<AuthRoute path="/searchResults" component={SearchResults} />
<AuthRoute>
<h1>404 Page Not Found</h1>
</AuthRoute>
</Switch>
</div>
);
}
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(`${Config.inventoryUrl}/${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 });
});
}
});
}, [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]);
});
}
};
return (
<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"
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, { useEffect, useState } from "react";
import ProductTable from "./ProductTable.jsx";
import Config from "../config.js";
import { Link } from "react-router-dom";
import Search from "./Search.jsx"
export default function ProductIndex() {
const [products, setProducts] = useState([]);
const [displayProducts, setDisplayProducts] = useState([]);
const [categories, setCategories] = useState([]);
const [activeCategory, setActiveCategory] = useState("");
// console.log(displayProducts);
const fetchProducts = async () => {
const res = await fetch(`${Config.inventoryUrl}`);
if (res.ok) {
const data = await res.json();
setProducts([...data]);
setDisplayProducts([...data]);
setCategories([
...data.reduce((acc, prod) => {
if (!acc.includes(prod.category)) {
acc.push(prod.category);
}
return acc;
}, []),
]);
}
};
useEffect(() => fetchProducts(), []);
return (
<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>
<select
className="form-select w-25 mt-1"
id="category-select"
onChange={(e) => {
if (e.target.value === "") {
setDisplayProducts([...products]);
return;
}
const filtered = products.filter(
(prod) => prod.category === e.target.value
);
setDisplayProducts([...filtered]);
}}
>
<option value="">Select Category</option>
{categories.map((category, i) => (
<option key={i + 679}>{category}</option>
))}
</select>
<Search />
<Link type="link" className="btn btn-success" to="/products/new">
+ New Product
</Link>
</div>
{products.length > 0 ? (
<>
<ProductTable
productData={displayProducts}
fetchProducts={fetchProducts}
products={displayProducts}
/>
</>
) : (
<p>No products found.</p>
)}
</div>
);
}
import React, { useState } from "react";
import "./../styles/ProductRow.css";
import { Modal, Button, Alert } from "react-bootstrap";
import { useHistory } from "react-router";
export default function ProductRow({ product, handleDelete }) {
const [show, setShow] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const history = useHistory();
const handleClose = () => {
setShow(false);
console.log({ show });
handleCloseConfirm();
};
const handleCloseDelete = (sku) => {
handleDelete(sku);
handleClose();
};
const handleShow = () => setShow(true);
const handleShowConfirm = () => setShowConfirm(true);
const handleCloseConfirm = () => setShowConfirm(false);
return (
<tr>
<td onClick={handleShow}>{product.sku}</td>
<td onClick={handleShow}>{product.upc}</td>
<td onClick={handleShow}>{product.productName}</td>
<td onClick={handleShow}>${product.price}</td>
<td onClick={handleShow}>{product.category}</td>
<td onClick={handleShow}>{product.availableStock}</td>
<td onClick={handleShow}>{product.blockedStock}</td>
<td onClick={handleShow}>{product.fulfilledStock}</td>
<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.availableStock}
<br />
Blocked Stock: {product.blockedStock}
<br />
Fulfilled Stock: {product.fulfilledStock}
</Modal.Body>
<Modal.Footer>
<Button
variant="danger"
className="float-left"
onClick={handleShowConfirm}
>
Delete product
</Button>
<Button
variant="primary"
onClick={() => history.push(`/products/${product.sku}/update`)}
>
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>
</tr>
);
}
import React, { useState, useEffect } from "react";
import ProductRow from "./ProductRow.jsx";
import { Container, Table } from "react-bootstrap";
import Config from "../config.js";
import { deleteProduct } from "../actions/apiRequests";
export default function ProductTable({ fetchProducts, products }) {
// const [products, setProducts] = useState([...productData]);
// 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 (
<Container id="prod-table" className="mt-3 mx-auto">
<Table>
<thead>
<tr>
<th>SKU</th>
<th>UPC</th>
<th>Product Name</th>
<th>Price</th>
<th>Category</th>
<th>Available Stock</th>
<th>Blocked Stock</th>
<th>Fulfilled Stock</th>
</tr>
</thead>
<tbody>
{products.map((product) => {
return (
<ProductRow key={product.sku} product={product} handleDelete={() => handleDelete(product.sku)} />
);
})}
</tbody>
</Table>
</Container>
);
}
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-primary" 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 ProductRow from "./ProductRow.jsx";
import { Table, Container } from "react-bootstrap";
import { deleteProduct } from "../actions/apiRequests";
import { withRouter } from "react-router";
import Search from "./Search.jsx";
class SearchResults extends React.Component {
constructor(props) {
super(props);
this.state = {
results: this.props.history.location.state.results
}
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(sku) {
deleteProduct(sku)
.then(() => {
const newResults = this.state.results.filter(product => product.sku !== sku);
this.setState({ results: newResults });
this.props.history.push("/searchResults", this.state);
});
}
render() {
return (
<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" >Search Results</h1>
<div><Search /></div>
</div>
{this.state.results.length > 0 ?
<Container id="prod-table" className="mt-3 mx-auto">
<Table>
<thead>
<tr>
<th>SKU</th>
<th>UPC</th>
<th>Product Name</th>
<th>Price</th>
<th>Category</th>
<th>Available Stock</th>
<th>Blocked Stock</th>
<th>Fulfilled Stock</th>
</tr>
</thead>
<tbody>
{this.state.results.map((product) => {
return (
<ProductRow key={product.sku} product={product} handleDelete={() => this.handleDelete(product.sku)} />
);
})}
</tbody>
</Table>
</Container>
:
<p>Unable to find any matching products.</p>
}
</div>
);
}
}
export default withRouter(SearchResults);
\ No newline at end of file
import {useState, useEffect} from 'react';
import Config from '../../config';
import "./promolistStyle.css";
import { NavLink } from 'react-router-dom';
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="container flex-column d-flex justify-content-center">
<div className="container mt-3 d-flex justify-content-between align-items-center">
<h1 className="promo-title"> Promotions</h1>
<NavLink className="add-navLink" to='/promos/new'><button className="btn 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 className="row-width">
</th>
<th className="row-width">
</th >
</tr>
</thead>
<tbody>
{promoData.length > 0 ? 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-color btn btn-primary btn-sm">Update</button>
</NavLink>
</td>
<td>
<button className="btn btn-danger btn-sm" onClick={() => deletePromotion(promo.promotionId)}>Delete</button>
</td>
</tr>
)
}).reverse() : <p>no promotions found.</p>}
</tbody>
</table>
</div>
</div>
)
}
/* .promo-container {
margin: 30px auto;
padding: 0 16px;
}
.promo-header {
display: flex;
justify-content: space-between;
padding-left: 10px;
padding-right: 10px;
} */
/*
.promo-title {
padding: 8px;
} */
.promo-index-list {
margin-top: 7px;
}
.promo-table-title {
border-bottom: 1px solid black;
}
.add-navLink {
align-self: center;
}
table {
border-collapse: collapse;
width: 100%;
}
.row-width {
width: 7%;
}
.btn-color {
background-color: #00567D;
}
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;
/* border-bottom: 1px black; */
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) => {
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) => {
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">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: 20, 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: "minimum quantity 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) => {
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)
}
register('productSku', { required: true })
setValue('productSku', props.location.state.promo.productSku);
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: 20, 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: "minimum quantity must be greater than 0" },
})}
id="minimumQuantity"
className="form-control"
type="number"
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;
max-width: 700px;
}
.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
import React, {useContext} from 'react';
import {GoogleLogin} from 'react-google-login';
import "./LoginButtonPage.css";
import {AuthContext} from "../../App";
const google_ClientID = process.env.REACT_APP_GOOGLE_CLIENT_ID;
const clientId = `${google_ClientID}.apps.googleusercontent.com`;
function LoginButton({}){
const { isLoggedIn, setIsLoggedIn,setUser } = useContext(AuthContext);
const onSuccessLogin = (res) =>{
setIsLoggedIn(true);
const { tokenId, profileObj } = res;
setUser(profileObj);
};
const onFailure = (res) =>{
setIsLoggedIn(false);
};
return(
<div>
<div className="loginContainer">
<h1>Welcome to Ascend Inventory</h1>
<h3>Please Login with Google credentials</h3>
<GoogleLogin
clientId= {clientId}
buttonText="Sign-in with Google"
onSuccess= {onSuccessLogin}
onFailure= {onFailure}
cookiePolicy={'single_host_origin'}
isSignedIn={true}
/>
</div>
</div>
);
}
export default LoginButton;
\ No newline at end of file
.loginContainer{
padding: 50px;
justify-content: center;
}
import React, {useContext} from 'react';
import {GoogleLogout} from 'react-google-login';
import {AuthContext} from "../../App";
import { Dropdown } from 'react-bootstrap';
import "./LogoutDropDown.css";
const google_ClientID = process.env.REACT_APP_GOOGLE_CLIENT_ID;
const clientId = `${google_ClientID}.apps.googleusercontent.com`;
function LogoutButton(){
const { isLoggedIn, setIsLoggedIn, user , setUser } = useContext(AuthContext);
const onSuccessLogout= (res) =>{
setIsLoggedIn(false);
}
return(
<div>
<Dropdown>
<Dropdown.Toggle className="dropdown">
{user != null ?
<p><img className="profile-pic" src={user.imageUrl}/>
{user.name}</p>
: <p>User</p>
}
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item>Profile</Dropdown.Item>
<Dropdown.Item><GoogleLogout
render={props => (<div onClick={props.onClick}>Logout</div>)}
clientId= {clientId}
buttonText="Logout"
onLogoutSuccess={onSuccessLogout}
isSignedIn={false}
/></Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
);
}
export default LogoutButton;
.profile-pic{
position: relative;
width: 28px;
height: 28px;
overflow: hidden;
border-radius: 50%;
padding: 2px;
}
.dropdown{
color:#EBEBEB;
background-color: transparent;
border-color: transparent;
}
.dropdown:hover{
color:#CCCDCF;
background-color: transparent;
border-color: transparent;
}
.dropdown-toggle:after {
display: none;
}
class Config {
static inventoryUrl = "http://localhost:8083/api/products";
static promotionsUrl = "http://localhost:8082/api/promos";
static sessionUrl = "http://localhost:8080//api/sessions";
}
export default Config;
const emptyProduct = {
sku: "",
upc: "",
productName: "",
brand: "",
category: "",
price: 0.0,
availableStock: 0,
productDesciption: "",
productImageUrl: "",
};
export default emptyProduct;
\ No newline at end of file
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import './index.css'; import 'bootstrap/dist/css/bootstrap.css';
import App from './App'; import "./index.css";
import reportWebVitals from './reportWebVitals'; import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
const storage = window.localStorage;
storage.setItem("loggedIn", "false");
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter>
<App /> <App />
</BrowserRouter>
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById('root')
); );
......
.navbar {
background-color: #00567D;
position: relative;
}
#page-title{
position: absolute;
display: flex;
justify-content: center;
align-items: center;
margin-top: auto;
margin-bottom: auto;
left: 0;
right: 0;
color: #EBEBEB;
pointer-events: none;
}
#logo{
width: 100px;
height: 50px;
}
.nav-link{
color: #EBEBEB !important;
}
.nav-link:hover{
color: #CCCDCF !important;
}
\ No newline at end of file
#title {
margin-left: 1em;
}
#prod-table {
margin-left: 5em;
}
\ No newline at end of file
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
.form-check-label{
color: rgba(0, 0, 0)
}
.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