├── .env ├── .gitignore ├── README.md ├── debug.log ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── font │ ├── apercu-bold.woff │ ├── apercu-medium.woff │ └── apercu-regular.woff ├── img │ └── customers.png ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── md-img │ ├── board.png │ ├── inventory.png │ ├── manage-order.png │ ├── order-control.png │ ├── orders.png │ ├── settings.png │ └── teams.png └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── Board ├── Board.js ├── Column.js └── Post.js ├── Common ├── AnimalAvatars.js ├── FieldModal.js ├── FieldRow.js ├── Loading.js ├── PageTitle.js ├── PrivateRoute.js ├── TableBuilder.js ├── WarningModal.js └── img │ ├── avatars │ ├── butterfly.png │ ├── deer.png │ ├── elephant.png │ ├── fox.png │ ├── frog.png │ ├── giraffe.png │ ├── hamster.png │ ├── lion.png │ ├── monkey.png │ ├── mouse.png │ ├── octopus.png │ ├── panda.png │ ├── pig.png │ ├── reindeer.png │ ├── rhino.png │ └── shark.png │ ├── board.png │ ├── change-password.png │ ├── construction.png │ ├── manage-accounts.png │ ├── new-product.png │ ├── orders.png │ ├── product-information.png │ ├── product-stock.png │ └── shopcast.svg ├── Home ├── Card.js └── Home.js ├── Inventory ├── CreateProduct.js ├── CreateProductForm.js ├── EmptyInventory.js ├── Form │ ├── Basics.js │ └── Media.js ├── Inventory.js ├── InventoryItem.js ├── ProductModal.js ├── RemoveProduct.js ├── SearchModal.js ├── UpdateProduct.js └── UpdateProductStock.js ├── Login ├── ErrorMessage.js ├── Login.js └── LoginCard.js ├── Orders ├── DraftOrder.js ├── Manage │ ├── Manage.js │ ├── Metadata.js │ └── New.js ├── Orders.js ├── OrdersStats.js ├── OrdersTable.js ├── Search.js └── Sidebar.js ├── Settings ├── SettingTabs.js ├── Settings.js ├── TabButton.js └── Views │ ├── Account.js │ ├── Notifications.js │ ├── Password.js │ └── UserGroups.js ├── Team ├── Member.js ├── Profile.js └── Team.js ├── index.css ├── index.js ├── logo.svg ├── serviceWorker.js ├── store ├── actions │ ├── auth.js │ ├── products.js │ └── types │ │ └── types.js └── reducers │ ├── auth.js │ ├── message.js │ ├── product.js │ └── rootReducer.js └── utils ├── getTokenTimeRemaining.js └── setAuthorizationToken.js /.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ecommerce Dashboard Frontend 2 | ## Board 3 | ![Board](https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/master/public/md-img/board.png "Board") 4 | 5 | ## Teams 6 | ![Team](https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/master/public/md-img/teams.png "Teams") 7 | 8 | ## View Orders 9 | ![View Orders](https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/master/public/md-img/orders.png "View Orders") 10 | 11 | ## Manage Order 12 | ![Manage Order](https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/master/public/md-img/manage-order.png "Manage Order") 13 | 14 | ## Settings 15 | ![Settings](https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/master/public/md-img/settings.png "Settings") 16 | 17 | ## Inventory 1 18 | ![Inventory1](https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/master/public/md-img/inventory.png "Inventory") 19 | 20 | # Details 21 | - Built with create-react-app (npm run start/dev). 22 | - Core UI library is Material-UI. 23 | - There is state management for managing user sessions via JWT. 24 | - This is only a front end service, you will need to deploy the backend service as well. -------------------------------------------------------------------------------- /debug.log: -------------------------------------------------------------------------------- 1 | [0827/122859.047:ERROR:crash_report_database_win.cc(469)] failed to stat report 2 | [0828/123901.964:ERROR:crash_report_database_win.cc(469)] failed to stat report 3 | [0828/193124.670:ERROR:crash_report_database_win.cc(469)] failed to stat report 4 | [0829/160303.694:ERROR:crash_report_database_win.cc(469)] failed to stat report 5 | [0829/183050.122:ERROR:crash_report_database_win.cc(469)] failed to stat report 6 | [0830/011400.286:ERROR:crash_report_database_win.cc(469)] failed to stat report 7 | [0830/011400.286:ERROR:crash_report_database_win.cc(469)] failed to stat report 8 | [0830/154404.160:ERROR:crash_report_database_win.cc(469)] failed to stat report 9 | [0830/154404.161:ERROR:crash_report_database_win.cc(469)] failed to stat report 10 | [0831/125110.278:ERROR:crash_report_database_win.cc(469)] failed to stat report 11 | [0831/125110.279:ERROR:crash_report_database_win.cc(469)] failed to stat report 12 | [0831/173628.955:ERROR:crash_report_database_win.cc(469)] failed to stat report 13 | [0831/173628.956:ERROR:crash_report_database_win.cc(469)] failed to stat report 14 | [0901/122449.896:ERROR:crash_report_database_win.cc(469)] failed to stat report 15 | [0901/122449.896:ERROR:crash_report_database_win.cc(469)] failed to stat report 16 | [0903/120725.284:ERROR:crash_report_database_win.cc(469)] failed to stat report 17 | [0903/120725.285:ERROR:crash_report_database_win.cc(469)] failed to stat report 18 | [0910/133514.933:ERROR:crash_report_database_win.cc(469)] failed to stat report 19 | [0910/133514.933:ERROR:crash_report_database_win.cc(469)] failed to stat report 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecommerce-dashboard-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.10.0", 7 | "@material-ui/icons": "^4.4.1", 8 | "@material-ui/lab": "^4.0.0-alpha.54", 9 | "axios": "^0.19.0", 10 | "faker": "^4.1.0", 11 | "jwt-decode": "^2.2.0", 12 | "react": "16.8.0", 13 | "react-dom": "16.8.0", 14 | "react-redux": "^7.1.1", 15 | "react-router-dom": "^5.2.0", 16 | "react-scripts": "3.1.1", 17 | "react-useanimations": "^1.2.14", 18 | "redux": "^4.0.4", 19 | "redux-thunk": "^2.3.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": "react-app" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/favicon.ico -------------------------------------------------------------------------------- /public/font/apercu-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/font/apercu-bold.woff -------------------------------------------------------------------------------- /public/font/apercu-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/font/apercu-medium.woff -------------------------------------------------------------------------------- /public/font/apercu-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/font/apercu-regular.woff -------------------------------------------------------------------------------- /public/img/customers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/img/customers.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | React App 26 | 29 | 30 | 31 | 32 | 33 |
34 | 44 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/md-img/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/board.png -------------------------------------------------------------------------------- /public/md-img/inventory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/inventory.png -------------------------------------------------------------------------------- /public/md-img/manage-order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/manage-order.png -------------------------------------------------------------------------------- /public/md-img/order-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/order-control.png -------------------------------------------------------------------------------- /public/md-img/orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/orders.png -------------------------------------------------------------------------------- /public/md-img/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/settings.png -------------------------------------------------------------------------------- /public/md-img/teams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/public/md-img/teams.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Apercu'; 3 | src: url('/font/apercu-regular.woff') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'ApercuBold'; 10 | src: url('/font/apercu-bold.woff') format('woff'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'ApercuMedium'; 17 | src: url('/font/apercu-medium.woff') format('woff'); 18 | font-weight: normal; 19 | font-style: normal; 20 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { Switch, Route, Link, BrowserRouter, Redirect } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | import { userSignOutRequest } from "./store/actions/auth"; 6 | 7 | import { makeStyles, createMuiTheme } from "@material-ui/core/styles"; 8 | import { ThemeProvider } from "@material-ui/styles"; 9 | import blue from "@material-ui/core/colors/blue"; 10 | import { 11 | Avatar, 12 | Modal, 13 | Backdrop, 14 | Fade, 15 | Typography, 16 | Box, 17 | Button, 18 | Paper, 19 | Tabs, 20 | Tab, 21 | } from "@material-ui/core"; 22 | import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; 23 | 24 | import Orders from "./Orders/Orders"; 25 | import Home from "./Home/Home"; 26 | import Inventory from "./Inventory/Inventory"; 27 | import Settings from "./Settings/Settings"; 28 | import Board from "./Board/Board"; 29 | import Login from "./Login/Login"; 30 | import Team from "./Team/Team"; 31 | 32 | import PrivateRoute from "./Common/PrivateRoute"; 33 | import getAvatar from "./Common/AnimalAvatars"; 34 | 35 | import "./App.css"; 36 | import MonkeyAvatar from "../src/Common/img/avatars/monkey.png"; 37 | 38 | const useStyles = makeStyles({ 39 | root: { 40 | flexGrow: 1, 41 | zIndex: 3000, 42 | boxShadow: "0 10px 20px rgba(0,0,0,0.025), 0 2px 2px rgba(0,0,0,0.05)", 43 | borderRadius: "0px", 44 | padding: "2px 0px 0px 5px", 45 | overflow: "hidden", 46 | // backgroundColor: '#1a237e', 47 | }, 48 | tab: { 49 | fontFamily: "ApercuMedium", 50 | textTransform: "none", 51 | // color: '#fff', 52 | color: "#525f7f", 53 | fontSize: "0.85rem", 54 | }, 55 | avatar: { 56 | width: 35, 57 | height: 35, 58 | backgroundColor: "none", 59 | }, 60 | avatarGroup: { 61 | position: "absolute", 62 | top: "0.5rem", 63 | left: "calc(100% - 75px)", 64 | color: "rgba(0,0,0,0.54)", 65 | "&:hover": { 66 | cursor: "pointer", 67 | color: blue[500], 68 | }, 69 | }, 70 | paper: { 71 | backgroundColor: "#fff", 72 | // boxShadow: theme.shadows[5], 73 | boxShadow: "0 20px 60px -2px rgba(27,33,58,.4)", 74 | padding: "2em", 75 | outline: "none", 76 | borderRadius: "8px", 77 | }, 78 | modal: { 79 | display: "flex", 80 | alignItems: "center", 81 | justifyContent: "center", 82 | }, 83 | title: { 84 | fontFamily: "ApercuMedium", 85 | marginTop: "2em", 86 | marginBottom: "2em", 87 | }, 88 | button: { 89 | boxShadow: "none", 90 | }, 91 | avatarIcon: { 92 | marginLeft: 5, 93 | marginTop: 5, 94 | }, 95 | }); 96 | 97 | const theme = createMuiTheme({ 98 | palette: { 99 | primary: { 100 | light: blue[300], 101 | main: blue[500], 102 | dark: blue[700], 103 | }, 104 | }, 105 | typography: { 106 | fontFamily: [ 107 | "Apercu", 108 | "-apple-system", 109 | "BlinkMacSystemFont", 110 | '"Segoe UI"', 111 | "Roboto", 112 | '"Helvetica Neue"', 113 | "Arial", 114 | "sans-serif", 115 | '"Apple Color Emoji"', 116 | '"Segoe UI Emoji"', 117 | '"Segoe UI Symbol"', 118 | ].join(","), 119 | }, 120 | overrides: { 121 | MuiOutlinedInput: { 122 | root: { 123 | "&:hover:not($disabled):not($focused):not($error) $notchedOutline": { 124 | border: "2px solid", 125 | borderColor: blue[200], 126 | }, 127 | }, 128 | }, 129 | MuiTableCell: { 130 | root: { 131 | borderBottom: "none", 132 | }, 133 | }, 134 | }, 135 | }); 136 | 137 | function App(props) { 138 | const classes = useStyles(); 139 | 140 | const { isAuthenticated, user } = props.auth; 141 | 142 | const [logoutModal, setLogoutModal] = React.useState(false); 143 | 144 | const RedirectToDashboard = () => ( 145 | 146 | {isAuthenticated ? : null} 147 | 148 | ); 149 | 150 | const handleLogoutOpen = () => { 151 | setLogoutModal(true); 152 | }; 153 | 154 | const handleLogoutClose = () => { 155 | setLogoutModal(false); 156 | }; 157 | 158 | const logout = (e) => { 159 | e.preventDefault(); 160 | handleLogoutClose(); 161 | props.userSignOutRequest(); 162 | }; 163 | 164 | const AvatarGroup = (props) => { 165 | return ( 166 | 172 | 173 | 174 | 175 | ); 176 | }; 177 | 178 | return ( 179 | 180 | 181 |
182 | 183 | 184 | ( 187 | 188 | 189 | 195 | 203 | 211 | 219 | 227 | 235 | 243 | 244 | 245 | 246 | 247 |
Inventory Wizard
} 251 | /> 252 | 253 | 254 | 255 | 260 | 265 |
266 |
267 | )} 268 | /> 269 | {isAuthenticated ? : null} 270 | 271 | 283 | 284 |
285 | 286 | Are you sure you want to sign out? 287 | 288 | 293 | 300 | 308 | 309 |
310 |
311 |
312 |
313 |
314 |
315 | ); 316 | } 317 | 318 | App.propTypes = { 319 | auth: PropTypes.object.isRequired, 320 | userSignOutRequest: PropTypes.func.isRequired, 321 | }; 322 | 323 | function mapStateToProps(state) { 324 | return { 325 | auth: state.auth, 326 | }; 327 | } 328 | 329 | export default connect(mapStateToProps, { userSignOutRequest })(App); 330 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/Board/Board.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { makeStyles, useTheme } from '@material-ui/core/styles'; 3 | import Container from '@material-ui/core/Container'; 4 | import { Typography, Grid } from '@material-ui/core'; 5 | 6 | import Column from './Column'; 7 | import PageTitle from './../Common/PageTitle'; 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | title: { 11 | fontFamily: 'ApercuMedium', 12 | marginTop: theme.spacing(4), 13 | marginBottom: theme.spacing(4), 14 | color: '#525f7f' 15 | }, 16 | })); 17 | 18 | export default function Board() { 19 | const classes = useStyles(); 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | } -------------------------------------------------------------------------------- /src/Board/Column.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Container from "@material-ui/core/Container"; 4 | import { Typography } from "@material-ui/core"; 5 | import Paper from "@material-ui/core/Paper"; 6 | import Button from "@material-ui/core/Button"; 7 | import Add from "@material-ui/icons/Add"; 8 | import Grid from "@material-ui/core/Grid"; 9 | import blue from "@material-ui/core/colors/blue"; 10 | 11 | import Post from "./Post"; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | paper: { 15 | border: "1px solid #e3f2fd", 16 | boxShadow: "0 0 11px #eaf0f6", 17 | marginBottom: "0.5rem", 18 | "&:hover": { 19 | backgroundColor: "#e3f2fd", 20 | color: "white", 21 | }, 22 | }, 23 | button: { 24 | backgroundColor: blue[100], 25 | "&:hover": { 26 | backgroundColor: theme.palette.primary.main, 27 | "& $addIcon": { 28 | color: "white", 29 | }, 30 | }, 31 | }, 32 | addIcon: { 33 | color: theme.palette.primary.main, 34 | }, 35 | })); 36 | 37 | const posts = [ 38 | { 39 | id: 12, 40 | title: "Review Fall/Winter Product Launch", 41 | content: "Meeting for Adidas 112, Bonobos 325, Calvin Klein 42.", 42 | date: "Oct 8", 43 | }, 44 | { 45 | id: 43, 46 | title: "Organize September 15 Incoming Shipment", 47 | content: "Expecting Hershal, Banana Republic top up.", 48 | date: "Sep 15", 49 | }, 50 | ]; 51 | 52 | export default function Board(props) { 53 | const classes = useStyles(); 54 | 55 | return ( 56 | 57 | 62 | {props.name} 63 | 64 | 65 | 68 | 69 | 76 | {posts.map((post) => ( 77 | 78 | 79 | 80 | ))} 81 | 82 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/Board/Post.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import clsx from "clsx"; 4 | import Card from "@material-ui/core/Card"; 5 | import CardHeader from "@material-ui/core/CardHeader"; 6 | import CardMedia from "@material-ui/core/CardMedia"; 7 | import CardContent from "@material-ui/core/CardContent"; 8 | import CardActions from "@material-ui/core/CardActions"; 9 | import Button from "@material-ui/core/Button"; 10 | import Avatar from "@material-ui/core/Avatar"; 11 | import IconButton from "@material-ui/core/IconButton"; 12 | import Typography from "@material-ui/core/Typography"; 13 | import { red } from "@material-ui/core/colors"; 14 | import CommentIcon from "@material-ui/icons/Comment"; 15 | import ThumbUpIcon from "@material-ui/icons/ThumbUp"; 16 | import ExpandMoreIcon from "@material-ui/icons/MoreHoriz"; 17 | 18 | import getAvatar from "../Common/AnimalAvatars"; 19 | 20 | const useStyles = makeStyles((theme) => ({ 21 | card: { 22 | border: "1px solid #eaf0f6", 23 | boxShadow: "0 0 11px #eaf0f6", 24 | }, 25 | media: { 26 | height: 0, 27 | paddingTop: "56.25%", // 16:9 28 | }, 29 | expand: { 30 | marginLeft: "auto", 31 | }, 32 | expandOpen: { 33 | transform: "rotate(180deg)", 34 | }, 35 | avatar: {}, 36 | rightIcon: { 37 | marginLeft: theme.spacing(1), 38 | }, 39 | button: { 40 | marginLeft: "auto", 41 | }, 42 | })); 43 | 44 | export default function Post(props) { 45 | const classes = useStyles(); 46 | 47 | return ( 48 | 49 | 56 | } 57 | action={ 58 | 59 | 60 | 61 | } 62 | title={props.title} 63 | subheader={props.date} 64 | /> 65 | 66 | 67 | {props.content} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/Common/AnimalAvatars.js: -------------------------------------------------------------------------------- 1 | import Butterfly from "../Common/img/avatars/butterfly.png"; 2 | import Deer from "../Common/img/avatars/deer.png"; 3 | import Elephant from "../Common/img/avatars/elephant.png"; 4 | import Fox from "../Common/img/avatars/fox.png"; 5 | import Frog from "../Common/img/avatars/frog.png"; 6 | import Giraffe from "../Common/img/avatars/giraffe.png"; 7 | import Hamster from "../Common/img/avatars/hamster.png"; 8 | import Lion from "../Common/img/avatars/lion.png"; 9 | import Monkey from "../Common/img/avatars/monkey.png"; 10 | import Mouse from "../Common/img/avatars/mouse.png"; 11 | import Octopus from "../Common/img/avatars/octopus.png"; 12 | import Panda from "../Common/img/avatars/panda.png"; 13 | import Pig from "../Common/img/avatars/pig.png"; 14 | import Reindeer from "../Common/img/avatars/reindeer.png"; 15 | import Rhino from "../Common/img/avatars/rhino.png"; 16 | import Shark from "../Common/img/avatars/shark.png"; 17 | 18 | const animals = [ 19 | Butterfly, 20 | Deer, 21 | Elephant, 22 | Fox, 23 | Frog, 24 | Giraffe, 25 | Hamster, 26 | Lion, 27 | Monkey, 28 | Mouse, 29 | Octopus, 30 | Panda, 31 | Pig, 32 | Reindeer, 33 | Rhino, 34 | Shark, 35 | ]; 36 | 37 | export default function getAvatar(index) { 38 | index = 39 | (index < animals.length ? index : null) || 40 | Math.floor(Math.random() * animals.length); 41 | return animals[index]; 42 | } 43 | -------------------------------------------------------------------------------- /src/Common/FieldModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles, TextField, Container, Typography, Divider, Box, IconButton, Button } from '@material-ui/core'; 3 | import ArrowBack from '@material-ui/icons/ArrowBack'; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | modal: { 7 | display: 'flex', 8 | alignItems: 'center', 9 | justifyContent: 'center', 10 | }, 11 | paper: { 12 | backgroundColor: theme.palette.background.paper, 13 | boxShadow: '0 20px 60px -2px rgba(27,33,58,.4)', 14 | padding: theme.spacing(2, 4, 3), 15 | borderRadius: '8px', 16 | minWidth: 550 17 | }, 18 | textfield: { 19 | 20 | }, 21 | label: { 22 | marginTop: 8 23 | }, 24 | button: { 25 | boxShadow: 'none', 26 | marginLeft: '1rem' 27 | } 28 | })); 29 | 30 | export default function FieldModal(props) { 31 | const classes = useStyles(); 32 | 33 | const [value, setValue] = React.useState(props.value); 34 | 35 | const handleChange = (e) => { 36 | setValue(e.target.value); 37 | } 38 | 39 | function Input() { 40 | if (props.variant === 'textarea') { 41 | return ( 42 | 54 | ) 55 | } else { 56 | return ( 57 | 67 | ) 68 | } 69 | } 70 | 71 | return ( 72 | 73 | 74 | 75 | 76 | 77 | {props.label} 78 | 79 | 80 | 81 | 82 | 85 | 88 | 89 | 90 | ); 91 | } -------------------------------------------------------------------------------- /src/Common/FieldRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | 4 | import { Grid, Typography, Divider, Box } from '@material-ui/core'; 5 | import { makeStyles } from '@material-ui/core/styles'; 6 | import Edit from '@material-ui/icons/KeyboardArrowRight'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | label: { 10 | letterSpacing: '.07272727em', 11 | fontSize: '.6875rem', 12 | fontWeight: 500, 13 | lineHeight: '1rem', 14 | textTransform: 'uppercase', 15 | color: '#5f6368', 16 | marginLeft: '10px', 17 | }, 18 | hover: { 19 | // margin: theme.spacing(1), 20 | '&:hover': { 21 | backgroundColor: '#f5f5f5', 22 | cursor: 'pointer' 23 | } 24 | }, 25 | row: { 26 | paddingTop: theme.spacing(1), 27 | }, 28 | icon: { 29 | color: '#5f6368', 30 | } 31 | })); 32 | 33 | const FieldRow = (props) => { 34 | const classes = useStyles(); 35 | 36 | const handleClick = () => { 37 | if (props.openModal) { 38 | props.openModal({ 39 | label: props.label, 40 | value: props.value 41 | }); 42 | } 43 | } 44 | 45 | return ( 46 |
47 | 48 | 49 | 50 | {props.label} 51 | 52 | 53 | 54 | 55 | {props.value} 56 | 57 | 58 | 59 | 60 | {props.openModal ? : null} 61 | 62 | 63 | 64 | 65 |
66 | ) 67 | } 68 | 69 | export default FieldRow; -------------------------------------------------------------------------------- /src/Common/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Grid from '@material-ui/core/Grid'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import CircularProgress from '@material-ui/core/CircularProgress'; 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | progress: { 8 | margin: theme.spacing(4), 9 | }, 10 | })); 11 | 12 | export default function Loading(props) { 13 | const classes = useStyles(); 14 | 15 | if (props.visible) { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } else { 24 | return null; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Common/PageTitle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | 4 | import { makeStyles, Box, IconButton, Typography } from "@material-ui/core"; 5 | import { ArrowBack } from "@material-ui/icons"; 6 | 7 | const useStyles = makeStyles((theme) => ({ 8 | container: { 9 | marginTop: theme.spacing(3), 10 | marginBottom: theme.spacing(3), 11 | }, 12 | title: { 13 | fontFamily: "ApercuMedium", 14 | color: "#525f7f", 15 | marginTop: 4, 16 | marginLeft: 4, 17 | }, 18 | })); 19 | 20 | export default function PageTitle(props) { 21 | const classes = useStyles(); 22 | const history = useHistory(); 23 | 24 | const changeRoute = () => { 25 | const route = props.route ? props.route : "/dashboard/home"; 26 | history.push(route); 27 | 28 | // Temporary workaround to allow another function to be passed via onClick prop 29 | if (props.onClick !== undefined) { 30 | props.onClick.call(this); 31 | } 32 | }; 33 | 34 | return ( 35 | 42 | 43 | 44 | 45 | 46 | {props.title} 47 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/Common/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from "react-router-dom"; 3 | 4 | export default function PrivateRoute({ component: Component, authed, ...rest }) { 5 | return ( 6 | authed === true 9 | ? 10 | : } 11 | /> 12 | ) 13 | } -------------------------------------------------------------------------------- /src/Common/TableBuilder.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | 4 | import { 5 | Table, 6 | TableHead, 7 | TableRow, 8 | TableCell, 9 | TableBody, 10 | Box, 11 | IconButton, 12 | Typography, 13 | makeStyles, 14 | Paper, 15 | } from "@material-ui/core"; 16 | 17 | import UnfoldMoreIcon from "@material-ui/icons/UnfoldMore"; 18 | import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight"; 19 | import blue from "@material-ui/core/colors/blue"; 20 | 21 | const useStyles = makeStyles((theme) => ({ 22 | tableHeadRow: { 23 | backgroundColor: "#fff", 24 | borderColor: "#fff", 25 | borderStyle: "solid", 26 | borderLeftWidth: "3px", 27 | }, 28 | tableHeadAvatar: { 29 | width: "50px", 30 | }, 31 | headerCell: { 32 | padding: theme.spacing(0), 33 | }, 34 | headerText: { 35 | fontFamily: "ApercuMedium", 36 | fontSize: "0.875rem", 37 | color: "#525f7f", 38 | paddingLeft: theme.spacing(2), 39 | paddingTop: theme.spacing(2), 40 | paddingBottom: theme.spacing(2), 41 | }, 42 | row: { 43 | borderColor: "#fff", 44 | borderStyle: "solid", 45 | borderLeftWidth: "3px", 46 | borderBottomWidth: "0px", 47 | borderTopWidth: "0px", 48 | borderRightWidth: "3px", 49 | "&:hover": { 50 | borderColor: theme.palette.primary.light, 51 | borderStyle: "solid", 52 | borderLeftWidth: "3px", 53 | backgroundColor: blue[50], 54 | borderRightColor: blue[50], 55 | }, 56 | }, 57 | root: { 58 | boxShadow: "0 0 11px #eaf0f6", 59 | borderRadius: "4px", 60 | overflow: "hidden", 61 | border: "1px solid #eaf0f6", 62 | }, 63 | })); 64 | 65 | const TableBuilder = (props) => { 66 | const classes = useStyles(); 67 | 68 | const { data } = props; 69 | 70 | const getKeys = () => { 71 | return Object.keys(data[0]); 72 | }; 73 | 74 | const getHeader = () => { 75 | const keys = getKeys(); 76 | 77 | return keys.map((key, index) => { 78 | return ( 79 | 80 | 81 | {key} 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | }); 89 | }; 90 | 91 | const RenderRow = (props) => { 92 | return props.keys.map((key, index) => { 93 | return {props.data[key]}; 94 | }); 95 | }; 96 | 97 | const getRowsData = () => { 98 | const keys = getKeys(); 99 | return data.map((row, index) => { 100 | return ( 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ); 110 | }); 111 | }; 112 | 113 | return ( 114 | 115 | 116 | 117 | 118 | {getHeader()} 119 | 120 | {/* 121 | 122 | Name 123 | 124 | 127 | 128 | 129 | */} 130 | 131 | 132 | 133 | 134 | 135 | {getRowsData()} 136 | {/* 137 | 138 | 139 | 140 | 141 | 142 | */} 143 | 144 |
145 |
146 | ); 147 | }; 148 | 149 | export default TableBuilder; 150 | -------------------------------------------------------------------------------- /src/Common/WarningModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import UseAnimations from "react-useanimations"; 4 | import { 5 | Modal, 6 | Fade, 7 | Box, 8 | Typography, 9 | Backdrop, 10 | Button, 11 | } from "@material-ui/core"; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | modal: { 15 | display: "flex", 16 | alignItems: "center", 17 | justifyContent: "center", 18 | }, 19 | paper: { 20 | backgroundColor: theme.palette.background.paper, 21 | boxShadow: "0 20px 60px -2px rgba(27,33,58,.4)", 22 | padding: theme.spacing(2, 4, 3), 23 | outline: "none", 24 | borderRadius: "8px", 25 | maxWidth: "450px", 26 | }, 27 | title: { 28 | fontFamily: "ApercuMedium", 29 | fontSize: "1.5em", 30 | marginLeft: theme.spacing(2), 31 | color: "#b71c1c", 32 | }, 33 | icon: { 34 | color: "#b71c1c", 35 | marginBottom: "-4px", 36 | }, 37 | circle: { 38 | backgroundColor: "#ffcdd2", 39 | width: "64px", 40 | height: "64px", 41 | borderRadius: "50%", 42 | display: "flex", 43 | justifyContent: "center", 44 | alignItems: "center", 45 | }, 46 | text: { 47 | marginTop: theme.spacing(2), 48 | }, 49 | actionBox: { 50 | marginTop: theme.spacing(4), 51 | }, 52 | abandon: { 53 | marginLeft: theme.spacing(2), 54 | paddingLeft: theme.spacing(2), 55 | paddingRight: theme.spacing(2), 56 | backgroundColor: "#ffcdd2", 57 | color: "#b71c1c", 58 | // margin: theme.spacing(1), 59 | borderRadius: "30px", 60 | "&:hover": { 61 | backgroundColor: "#b71c1c", 62 | color: "white", 63 | }, 64 | }, 65 | })); 66 | 67 | export default function WarningModal(props) { 68 | const { open, setOpen, quit } = props; 69 | 70 | const defaultDetails = { 71 | animationKey: "alertCircle", 72 | title: "Modal Title", 73 | subtitle: ["Subtitle1", "Subtitle2"], 74 | action: "Quit", 75 | }; 76 | 77 | const details = props.details || defaultDetails; 78 | 79 | const handleClose = () => { 80 | setOpen(false); 81 | }; 82 | 83 | const classes = useStyles(); 84 | 85 | return ( 86 | 98 | 99 |
100 | 101 |
102 | 107 |
108 | {details.title} 109 |
110 | {details.subtitle.map((text, key) => ( 111 | 112 | {text} 113 | 114 | ))} 115 | 120 | 121 | 124 | 125 |
126 |
127 |
128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /src/Common/img/avatars/butterfly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/butterfly.png -------------------------------------------------------------------------------- /src/Common/img/avatars/deer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/deer.png -------------------------------------------------------------------------------- /src/Common/img/avatars/elephant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/elephant.png -------------------------------------------------------------------------------- /src/Common/img/avatars/fox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/fox.png -------------------------------------------------------------------------------- /src/Common/img/avatars/frog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/frog.png -------------------------------------------------------------------------------- /src/Common/img/avatars/giraffe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/giraffe.png -------------------------------------------------------------------------------- /src/Common/img/avatars/hamster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/hamster.png -------------------------------------------------------------------------------- /src/Common/img/avatars/lion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/lion.png -------------------------------------------------------------------------------- /src/Common/img/avatars/monkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/monkey.png -------------------------------------------------------------------------------- /src/Common/img/avatars/mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/mouse.png -------------------------------------------------------------------------------- /src/Common/img/avatars/octopus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/octopus.png -------------------------------------------------------------------------------- /src/Common/img/avatars/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/panda.png -------------------------------------------------------------------------------- /src/Common/img/avatars/pig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/pig.png -------------------------------------------------------------------------------- /src/Common/img/avatars/reindeer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/reindeer.png -------------------------------------------------------------------------------- /src/Common/img/avatars/rhino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/rhino.png -------------------------------------------------------------------------------- /src/Common/img/avatars/shark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/avatars/shark.png -------------------------------------------------------------------------------- /src/Common/img/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/board.png -------------------------------------------------------------------------------- /src/Common/img/change-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/change-password.png -------------------------------------------------------------------------------- /src/Common/img/construction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/construction.png -------------------------------------------------------------------------------- /src/Common/img/manage-accounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/manage-accounts.png -------------------------------------------------------------------------------- /src/Common/img/new-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/new-product.png -------------------------------------------------------------------------------- /src/Common/img/orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/orders.png -------------------------------------------------------------------------------- /src/Common/img/product-information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/product-information.png -------------------------------------------------------------------------------- /src/Common/img/product-stock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisrch/ecommerce-dashboard-react/a111b8d2dc00889e38be622851bba5bb2e7a516a/src/Common/img/product-stock.png -------------------------------------------------------------------------------- /src/Common/img/shopcast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Home/Card.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActionArea from '@material-ui/core/CardActionArea'; 5 | import CardActions from '@material-ui/core/CardActions'; 6 | import CardContent from '@material-ui/core/CardContent'; 7 | import CardMedia from '@material-ui/core/CardMedia'; 8 | import Button from '@material-ui/core/Button'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import { Link } from 'react-router-dom'; 11 | 12 | const useStyles = makeStyles({ 13 | card: { 14 | maxWidth: 345, 15 | boxShadow: '0 0 1px 0 rgba(0,0,0,.22)', 16 | // boxShadow: '0 0.938em 1.588em rgba(50,50,93,.1), 0 0.313em 0.938em rgba(0,0,0,.07)' 17 | }, 18 | media: { 19 | userSelect: 'none', 20 | pointerEvents: 'none', 21 | } 22 | }); 23 | 24 | export default function ImgMediaCard(props) { 25 | const classes = useStyles(); 26 | 27 | return ( 28 | 29 | 36 | 37 | 38 | {props.title} 39 | 40 | 41 | {props.description} 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | ); 52 | } -------------------------------------------------------------------------------- /src/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Container from '@material-ui/core/Container'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Card from './Card'; 5 | import Grid from '@material-ui/core/Grid'; 6 | import Typography from '@material-ui/core/Typography'; 7 | 8 | import Orders from '../Common/img/orders.png'; 9 | import Board from '../Common/img/board.png'; 10 | import NewProduct from '../Common/img/new-product.png'; 11 | import ChangePassword from '../Common/img/change-password.png'; 12 | import ManageAccounts from '../Common/img/manage-accounts.png'; 13 | import ProductInformation from '../Common/img/product-information.png'; 14 | import ProductStock from '../Common/img/product-stock.png'; 15 | 16 | const useStyles = makeStyles(theme => ({ 17 | container: { 18 | marginBottom: '5em' 19 | }, 20 | title: { 21 | fontFamily: 'ApercuMedium', 22 | marginTop: theme.spacing(4), 23 | marginBottom: theme.spacing(4), 24 | } 25 | })); 26 | 27 | function Home(props) { 28 | const [count, setCount] = useState(0); 29 | const classes = useStyles(); 30 | 31 | return ( 32 | 33 | Dashboards 34 | 35 | 36 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | Inventory 56 | 57 | 58 | 65 | 66 | 67 | 74 | 75 | 76 | 83 | 84 | 85 | 86 | Account 87 | 88 | 89 | 96 | 97 | 98 | 105 | 106 | 107 | 108 | ); 109 | } 110 | 111 | export default Home; -------------------------------------------------------------------------------- /src/Inventory/CreateProduct.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Button from "@material-ui/core/Button"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import Icon from "@material-ui/core/Icon"; 5 | import AddIcon from "@material-ui/icons/Add"; 6 | import PublishIcon from "@material-ui/icons/Publish"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | button: { 10 | boxShadow: "none", 11 | fontFamily: "ApercuMedium", 12 | }, 13 | leftIcon: { 14 | marginRight: theme.spacing(1), 15 | }, 16 | rightIcon: { 17 | marginLeft: theme.spacing(1), 18 | }, 19 | iconSmall: { 20 | fontSize: 20, 21 | }, 22 | })); 23 | 24 | export default function CreateProduct(props) { 25 | const classes = useStyles(); 26 | 27 | return ( 28 | 29 | 39 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/Inventory/CreateProductForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container, Box, makeStyles, Button, Stepper, Step, StepLabel, Grid } from "@material-ui/core"; 3 | 4 | import Basics from './Form/Basics'; 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | grid: { 8 | flexGrow: 1, 9 | marginBottom: theme.spacing(1), 10 | marginTop: theme.spacing(1) 11 | }, 12 | box: { 13 | }, 14 | root: { 15 | width: "80vw", 16 | overflow: "scroll" 17 | }, 18 | formControl: { 19 | minWidth: 120, 20 | marginTop: theme.spacing(2) 21 | }, 22 | button: { 23 | boxShadow: 'none', 24 | }, 25 | content: { 26 | minHeight: 400, 27 | } 28 | })); 29 | 30 | function getSteps() { 31 | return ['Basics', 'Media', 'Pricing', 'Shipping', 'Inventory']; 32 | } 33 | 34 | export default function CreateProductForm() { 35 | const classes = useStyles(); 36 | 37 | const [product, setProduct] = React.useState({ 38 | name: '', 39 | brand: '', 40 | description: '', 41 | category: '', 42 | }); 43 | 44 | function getStepContent(stepIndex) { 45 | switch (stepIndex) { 46 | case 0: 47 | return ; 48 | case 1: 49 | return 'What is an ad group anyways?'; 50 | case 2: 51 | return 'This is the bit I really care about!'; 52 | default: 53 | return 'Uknown stepIndex'; 54 | } 55 | } 56 | 57 | 58 | const [activeStep, setActiveStep] = React.useState(0); 59 | const steps = getSteps(); 60 | 61 | const handleNext = () => { 62 | setActiveStep(prevActiveStep => prevActiveStep + 1); 63 | }; 64 | 65 | const handleBack = () => { 66 | setActiveStep(prevActiveStep => prevActiveStep - 1); 67 | }; 68 | 69 | const handleReset = () => { 70 | setActiveStep(0); 71 | }; 72 | 73 | return ( 74 | 75 |
76 | 77 | {steps.map(label => ( 78 | 79 | {label} 80 | 81 | ))} 82 | 83 |
84 | 85 | {getStepContent(activeStep)} 86 | 87 | 88 | 89 | 90 | 93 | 94 | 95 | 96 | {activeStep > 0 ? : null} 97 | {activeStep === steps.length - 1 ? 98 | ( 99 | 102 | ) : ( 103 | 106 | )} 107 | 108 | 109 | 110 | 111 | 112 | 113 | { /* 114 | 115 | 116 | 117 | 118 | Pricing 119 | 120 | 121 | 122 | 123 | 124 | €, 132 | }} 133 | /> 134 | 135 | 136 | 147 | } 148 | label="Charge tax on product" 149 | /> 150 | 151 | 152 | 153 | 154 | */ } 155 | 156 |
157 | ); 158 | } 159 | -------------------------------------------------------------------------------- /src/Inventory/EmptyInventory.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Container from '@material-ui/core/Container'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import InboxIcon from '@material-ui/icons/Inbox'; 6 | import { Box } from '@material-ui/core'; 7 | import Construction from '../Common/img/construction.png'; 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | image: { 11 | width: 600 12 | }, 13 | bold: { 14 | fontFamily: 'ApercuBold', 15 | } 16 | })); 17 | 18 | 19 | export default function EmptyInventory() { 20 | 21 | const classes = useStyles(); 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | Inventory is empty. 31 | 32 | 33 | Create New Product to add new items to Inventory. 34 | 35 | 36 | 37 | ) 38 | } -------------------------------------------------------------------------------- /src/Inventory/Form/Basics.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles, Grid, Box, Typography, TextField, FormControl, InputLabel, Select, MenuItem } from '@material-ui/core'; 3 | 4 | const useStyles = makeStyles((theme) => ({ 5 | title: { 6 | fontFamily: 'ApercuMedium' 7 | }, 8 | formControl: { 9 | minWidth: 120, 10 | marginTop: theme.spacing(2) 11 | }, 12 | })); 13 | 14 | export default function Basics(props) { 15 | const classes = useStyles(); 16 | 17 | const inputLabel = React.useRef(null); 18 | const [labelWidth, setLabelWidth] = React.useState(0); 19 | React.useEffect(() => { 20 | setLabelWidth(inputLabel.current.offsetWidth); 21 | }, []); 22 | 23 | return ( 24 | 25 | 26 | 27 | Basics 28 | Name, brand, and description let shoppers quickly scan through each product. 29 | 30 | 31 | 32 | 33 | 40 | 41 | 42 | 49 | 50 | 51 | 62 | 63 | 64 | 65 | 66 | Category 67 | 68 | 86 | 87 | 88 | 89 | 90 | ); 91 | } -------------------------------------------------------------------------------- /src/Inventory/Form/Media.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles, Grid, Box, Typography } from '@material-ui/core'; 3 | 4 | const useStyles = makeStyles((theme) => ({ 5 | title: { 6 | fontFamily: 'ApercuMedium' 7 | }, 8 | formControl: { 9 | minWidth: 120, 10 | marginTop: theme.spacing(2) 11 | }, 12 | })); 13 | 14 | export default function Media() { 15 | const classes = useStyles(); 16 | 17 | return ( 18 | 19 | 20 | 21 | Basics 22 | Name, brand, and description let shoppers quickly scan through each product. 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } -------------------------------------------------------------------------------- /src/Inventory/Inventory.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | import { fetchProducts } from "../store/actions/products"; 5 | 6 | import { makeStyles, useTheme } from "@material-ui/core/styles"; 7 | import IconButton from "@material-ui/core/IconButton"; 8 | import Grid from "@material-ui/core/Grid"; 9 | import Container from "@material-ui/core/Container"; 10 | import Typography from "@material-ui/core/Typography"; 11 | 12 | import Fab from "@material-ui/core/Fab"; 13 | import Zoom from "@material-ui/core/Zoom"; 14 | import Modal from "@material-ui/core/Modal"; 15 | import Backdrop from "@material-ui/core/Backdrop"; 16 | import Fade from "@material-ui/core/Fade"; 17 | import Paper from "@material-ui/core/Paper"; 18 | import Button from "@material-ui/core/Button"; 19 | 20 | import SearchIcon from "@material-ui/icons/Search"; 21 | import ViewModuleIcon from "@material-ui/icons/ViewModule"; 22 | import ViewHeadlineIcon from "@material-ui/icons/ViewHeadline"; 23 | import RefreshIcon from "@material-ui/icons/Refresh"; 24 | 25 | import CreateProduct from "./CreateProduct"; 26 | import InventoryItem from "./InventoryItem"; 27 | import EmptyInventory from "./EmptyInventory"; 28 | import SearchModal from "./SearchModal"; 29 | import ProductModal from "./ProductModal"; 30 | import CreateProductForm from "./CreateProductForm"; 31 | import PageTitle from "./../Common/PageTitle"; 32 | 33 | const useStyles = makeStyles((theme) => ({ 34 | fab: { 35 | margin: 0, 36 | top: "auto", 37 | left: "auto", 38 | position: "fixed", 39 | bottom: theme.spacing(7), 40 | right: theme.spacing(7), 41 | }, 42 | action: { 43 | marginLeft: "auto", 44 | marginTop: "0.8rem", 45 | marginRight: theme.spacing(2), 46 | }, 47 | modal: { 48 | display: "flex", 49 | alignItems: "center", 50 | justifyContent: "center", 51 | }, 52 | createProductModal: { 53 | display: "flex", 54 | alignItems: "center", 55 | justifyContent: "center", 56 | overflow: "scroll", 57 | }, 58 | paper: { 59 | backgroundColor: theme.palette.background.paper, 60 | // boxShadow: theme.shadows[5], 61 | boxShadow: "0 20px 60px -2px rgba(27,33,58,.4)", 62 | padding: theme.spacing(2, 4, 3), 63 | outline: "none", 64 | borderRadius: "8px", 65 | }, 66 | emptyIcon: { 67 | color: "#00000032", 68 | fontSize: "10em", 69 | }, 70 | emptyContainer: { 71 | marginTop: "25vh", 72 | }, 73 | title: { 74 | fontFamily: "ApercuMedium", 75 | marginTop: theme.spacing(4), 76 | marginBottom: theme.spacing(4), 77 | }, 78 | button: { 79 | margin: theme.spacing(1), 80 | }, 81 | toolbar: { 82 | // boxShadow: '0 0 1px 0 rgba(0,0,0,.22)', 83 | boxShadow: "0 0 11px #eaf0f6", 84 | display: "inline-block", 85 | marginBottom: theme.spacing(1), 86 | width: "100%", 87 | }, 88 | lastUpdated: { 89 | marginTop: theme.spacing(2), 90 | padding: 0, 91 | color: "rgb(112, 117, 122)", 92 | }, 93 | })); 94 | 95 | const Inventory = (props) => { 96 | const classes = useStyles(); 97 | const theme = useTheme(); 98 | const [open, setOpen] = React.useState(false); 99 | const [product, setProduct] = React.useState({ 100 | id: "ID", 101 | name: "Name", 102 | type: "Type", 103 | description: "Description", 104 | }); 105 | 106 | const [searchModal, setSearchModal] = React.useState(false); 107 | const [createProductModal, setCreateProductModal] = React.useState(false); 108 | const [lastUpdatedTime, setLastUpdatedTime] = React.useState("N/A"); 109 | 110 | React.useEffect(() => { 111 | props.dispatch(fetchProducts()); 112 | setLastUpdatedTime(`${new Date().toLocaleString()}`); 113 | }, []); 114 | 115 | const openCreateNewProductModal = () => { 116 | setCreateProductModal(true); 117 | }; 118 | 119 | const closeCreateNewProductModal = () => { 120 | setCreateProductModal(false); 121 | }; 122 | 123 | const openSearchModal = () => { 124 | setSearchModal(true); 125 | }; 126 | 127 | const closeSearchModal = () => { 128 | setSearchModal(false); 129 | }; 130 | 131 | const handleOpen = (product) => { 132 | setProduct(product); 133 | setOpen(true); 134 | }; 135 | 136 | const handleClose = () => { 137 | setOpen(false); 138 | }; 139 | 140 | const transitionDuration = { 141 | enter: theme.transitions.duration.enteringScreen, 142 | exit: theme.transitions.duration.leavingScreen, 143 | }; 144 | 145 | return ( 146 | 147 | 148 | 149 | 150 |
151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
162 |
163 | 164 |
165 |
166 |
167 | {props.products.length === 0 || props.products.length === null ? ( 168 | 169 | ) : ( 170 | 171 | 172 | {props.products.map((product) => ( 173 | 174 | 175 | 176 | ))} 177 | 178 | 179 | 180 | Inventory up to date. Last retrieved at {lastUpdatedTime} 181 | 182 | 183 | 184 | )} 185 |
186 | 187 | 195 | 201 | 202 | 203 | 204 | 205 | 217 | 218 |
219 | 220 |
221 |
222 |
223 | 224 | {/* Search Modal */} 225 | 237 | 238 |
239 | 240 |
241 |
242 |
243 | 244 | {/* Create Product Modal */} 245 | 257 | 258 |
259 | 260 |
261 |
262 |
263 |
264 | ); 265 | }; 266 | 267 | Inventory.defaultProps = { 268 | products: [], 269 | }; 270 | 271 | Inventory.propTypes = { 272 | products: PropTypes.array.isRequired, 273 | }; 274 | 275 | function mapStateToProps(state) { 276 | return { 277 | products: state.product.products, 278 | }; 279 | } 280 | 281 | export default connect(mapStateToProps, null)(Inventory); 282 | -------------------------------------------------------------------------------- /src/Inventory/InventoryItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActions from '@material-ui/core/CardActions'; 5 | import CardContent from '@material-ui/core/CardContent'; 6 | import Button from '@material-ui/core/Button'; 7 | import Typography from '@material-ui/core/Typography'; 8 | import Grid from '@material-ui/core/Grid'; 9 | 10 | const useStyles = makeStyles({ 11 | card: { 12 | minWidth: 275, 13 | // boxShadow: '0 0 1px 0 rgba(0,0,0,.22)' 14 | boxShadow: '0 0 11px #eaf0f6', 15 | }, 16 | bullet: { 17 | display: 'inline-block', 18 | margin: '0 2px', 19 | transform: 'scale(0.8)', 20 | }, 21 | title: { 22 | fontSize: 14, 23 | }, 24 | subtitle: { 25 | fontFamily: 'ApercuBold' 26 | } 27 | }); 28 | 29 | export default function InventoryItem(props) { 30 | const classes = useStyles(); 31 | 32 | const openProductModal = () => { 33 | props.openModal(props.item); 34 | } 35 | 36 | const closeProductModal = () => { 37 | console.log('closing modal'); 38 | props.closeModal(); 39 | } 40 | 41 | return ( 42 | 43 | 44 | 45 | {props.item.name} 46 | 47 | 48 | 49 | 50 | Brand 51 | 52 | 53 | {props.item.brand} 54 | 55 | 56 | 57 | 58 | ID 59 | 60 | 61 | {props.item.id} 62 | 63 | 64 | 65 | 66 | Category 67 | 68 | 69 | {props.item.category} 70 | 71 | 72 | 73 | 74 | Stock 75 | 76 | 77 | {Math.floor(Math.random() * 100)} 78 | 79 | 80 | 81 | 82 | 83 |
84 |
85 |
86 | 87 | 88 | 89 |
90 | ); 91 | } -------------------------------------------------------------------------------- /src/Inventory/ProductModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Button from '@material-ui/core/Button'; 5 | import Box from '@material-ui/core/Box'; 6 | import Grid from '@material-ui/core/Grid'; 7 | import Typography from '@material-ui/core/Typography'; 8 | import { Container, Modal } from '@material-ui/core'; 9 | 10 | import UpdateProduct from './UpdateProduct'; 11 | import UpdateProductStock from './UpdateProductStock'; 12 | import RemoveProduct from './RemoveProduct'; 13 | import FieldRow from './../Common/FieldRow'; 14 | import FieldModal from './../Common/FieldModal'; 15 | 16 | const useStyles = makeStyles(theme => ({ 17 | container: { 18 | padding: theme.spacing(1), 19 | minWidth: '25vw' 20 | }, 21 | textField: { 22 | marginRight: theme.spacing(1), 23 | width: '375px' 24 | }, 25 | button: { 26 | boxShadow: 'none', 27 | }, 28 | title: { 29 | fontFamily: 'ApercuMedium', 30 | marginTop: theme.spacing(2), 31 | marginBottom: theme.spacing(2) 32 | }, 33 | subtitle: { 34 | fontFamily: 'ApercuBold' 35 | }, 36 | image: { 37 | width: '10vw', 38 | boxShadow: '0 0 1px 0 rgba(0,0,0,.22)', 39 | padding: 15, 40 | marginBottom: theme.spacing(2), 41 | borderRadius: 4 42 | }, 43 | modal: { 44 | display: 'flex', 45 | alignItems: 'center', 46 | justifyContent: 'center', 47 | }, 48 | paper: { 49 | backgroundColor: theme.palette.background.paper, 50 | // boxShadow: theme.shadows[5], 51 | boxShadow: '0 20px 60px -2px rgba(27,33,58,.4)', 52 | padding: theme.spacing(2, 4, 3), 53 | outline: 'none', 54 | borderRadius: '8px' 55 | }, 56 | })); 57 | 58 | 59 | export default function ProductModal(props) { 60 | const classes = useStyles(); 61 | 62 | const [fieldModal, setFieldModal] = React.useState({ 63 | open: false, 64 | field: { 65 | label: null, 66 | value: null 67 | } 68 | }); 69 | 70 | const [product, setProduct] = React.useState({ 71 | ...props.item 72 | }) 73 | 74 | const showFieldModal = (field) => { 75 | setFieldModal({ 76 | open: true, 77 | field, 78 | }); 79 | } 80 | 81 | const handleClose = () => { 82 | setFieldModal({ 83 | ...fieldModal, 84 | open: false 85 | }); 86 | } 87 | 88 | return ( 89 | 90 | {props.item.name} 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 110 | 111 | 112 | 113 | 124 |
125 | 126 |
127 |
128 | 129 |
130 | ); 131 | } -------------------------------------------------------------------------------- /src/Inventory/RemoveProduct.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Icon from '@material-ui/core/Icon'; 5 | import DeleteForever from '@material-ui/icons/DeleteForever'; 6 | import red from '@material-ui/core/colors/red'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | leftIcon: { 10 | marginRight: theme.spacing(1), 11 | }, 12 | rightIcon: { 13 | marginLeft: theme.spacing(1), 14 | }, 15 | iconSmall: { 16 | fontSize: 20, 17 | }, 18 | dangerButton: { 19 | borderColor: red[500], 20 | color: red[500], 21 | '&:hover': { 22 | backgroundColor: red[50] 23 | }, 24 | fontFamily: 'ApercuMedium', 25 | }, 26 | })); 27 | 28 | export default function CreateProduct() { 29 | const classes = useStyles(); 30 | 31 | return ( 32 | 36 | ) 37 | } -------------------------------------------------------------------------------- /src/Inventory/SearchModal.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import MenuItem from '@material-ui/core/MenuItem'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import Button from '@material-ui/core/Button'; 6 | import Box from '@material-ui/core/Box'; 7 | import SearchIcon from '@material-ui/icons/Search'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import InputAdornment from '@material-ui/core/InputAdornment'; 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | container: { 13 | display: 'flex', 14 | flexWrap: 'wrap', 15 | }, 16 | textField: { 17 | marginRight: theme.spacing(1), 18 | width: '375px' 19 | }, 20 | typeField: { 21 | marginLeft: theme.spacing(1), 22 | width: '175px' 23 | }, 24 | dense: { 25 | marginTop: theme.spacing(2), 26 | }, 27 | menu: { 28 | width: 200, 29 | }, 30 | rightIcon: { 31 | marginLeft: theme.spacing(1), 32 | }, 33 | button: { 34 | boxShadow: 'none', 35 | }, 36 | title: { 37 | fontFamily: 'ApercuMedium', 38 | marginTop: theme.spacing(4), 39 | marginBottom: theme.spacing(4) 40 | } 41 | })); 42 | 43 | 44 | 45 | export default function Search(props) { 46 | const classes = useStyles(); 47 | const [values, setValues] = React.useState({ 48 | type: 'ID', 49 | }); 50 | 51 | const [queryValue, setQueryValue] = React.useState(''); 52 | 53 | const attributes = [ 54 | { 55 | value: 'ID', 56 | label: 'ID', 57 | }, 58 | { 59 | value: 'Name', 60 | label: 'Name', 61 | }, 62 | { 63 | value: 'Type', 64 | label: 'Type', 65 | }, 66 | ]; 67 | 68 | function handleChange(event) { 69 | setValues(oldValues => ({ 70 | type: event.target.value, 71 | })); 72 | } 73 | 74 | function handleQueryValue(e) { 75 | setQueryValue(e.target.value); 76 | } 77 | 78 | return ( 79 |
80 | Search Inventory 81 | 93 | 94 | 95 | ), 96 | }} 97 | /> 98 | 113 | {attributes.map(option => ( 114 | 115 | {option.label} 116 | 117 | ))} 118 | 119 | 120 | 121 | 124 | 127 | 128 | 129 |
130 | ); 131 | } -------------------------------------------------------------------------------- /src/Inventory/UpdateProduct.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Icon from '@material-ui/core/Icon'; 5 | import Edit from '@material-ui/icons/Edit'; 6 | import BlueGrey from '@material-ui/core/colors/blueGrey'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | button: { 10 | fontFamily: 'ApercuMedium', 11 | }, 12 | rightIcon: { 13 | marginLeft: theme.spacing(1), 14 | }, 15 | iconSmall: { 16 | fontSize: 20, 17 | }, 18 | })); 19 | 20 | export default function UpdateProduct() { 21 | const classes = useStyles(); 22 | 23 | return ( 24 | 28 | ) 29 | } -------------------------------------------------------------------------------- /src/Inventory/UpdateProductStock.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Icon from '@material-ui/core/Icon'; 5 | import UnarchiveIcon from '@material-ui/icons/Unarchive'; 6 | import BlueGrey from '@material-ui/core/colors/blueGrey'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | button: { 10 | fontFamily: 'ApercuMedium', 11 | }, 12 | rightIcon: { 13 | marginLeft: theme.spacing(1), 14 | }, 15 | iconSmall: { 16 | fontSize: 20, 17 | }, 18 | })); 19 | 20 | export default function UpdateProductStock() { 21 | const classes = useStyles(); 22 | 23 | return ( 24 | 28 | ) 29 | } -------------------------------------------------------------------------------- /src/Login/ErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import clsx from 'clsx'; 4 | import Button from '@material-ui/core/Button'; 5 | import CheckCircleIcon from '@material-ui/icons/CheckCircle'; 6 | import ErrorIcon from '@material-ui/icons/Error'; 7 | import InfoIcon from '@material-ui/icons/Info'; 8 | import CloseIcon from '@material-ui/icons/Close'; 9 | import { amber, green } from '@material-ui/core/colors'; 10 | import IconButton from '@material-ui/core/IconButton'; 11 | import Snackbar from '@material-ui/core/Snackbar'; 12 | import SnackbarContent from '@material-ui/core/SnackbarContent'; 13 | import WarningIcon from '@material-ui/icons/Warning'; 14 | import { makeStyles } from '@material-ui/core/styles'; 15 | 16 | const variantIcon = { 17 | success: CheckCircleIcon, 18 | warning: WarningIcon, 19 | error: ErrorIcon, 20 | info: InfoIcon, 21 | }; 22 | 23 | const useStyles1 = makeStyles(theme => ({ 24 | success: { 25 | backgroundColor: green[600], 26 | }, 27 | error: { 28 | color: '#b71c1c', 29 | backgroundColor: '#ffcdd2' 30 | }, 31 | info: { 32 | backgroundColor: theme.palette.primary.main, 33 | }, 34 | warning: { 35 | backgroundColor: amber[700], 36 | }, 37 | icon: { 38 | fontSize: 20, 39 | }, 40 | iconVariant: { 41 | opacity: 0.9, 42 | marginRight: theme.spacing(1), 43 | }, 44 | message: { 45 | display: 'flex', 46 | alignItems: 'center', 47 | }, 48 | })); 49 | 50 | function MySnackbarContentWrapper(props) { 51 | const classes = useStyles1(); 52 | const { className, message, onClose, variant, ...other } = props; 53 | const Icon = variantIcon[variant]; 54 | 55 | return ( 56 | 61 | 62 | {message} 63 | 64 | } 65 | {...other} 66 | /> 67 | ); 68 | } 69 | 70 | MySnackbarContentWrapper.propTypes = { 71 | className: PropTypes.string, 72 | message: PropTypes.string, 73 | onClose: PropTypes.func, 74 | variant: PropTypes.oneOf(['error', 'info', 'success', 'warning']).isRequired, 75 | }; 76 | 77 | const useStyles2 = makeStyles(theme => ({ 78 | margin: { 79 | margin: theme.spacing(1), 80 | boxShadow: 'none' 81 | }, 82 | })); 83 | 84 | export default function CustomizedSnackbars(props) { 85 | const classes = useStyles2(); 86 | 87 | return ( 88 | 93 | ); 94 | } -------------------------------------------------------------------------------- /src/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | 5 | import Container from "@material-ui/core/Container"; 6 | import { makeStyles } from "@material-ui/core/styles"; 7 | import { Grid } from "@material-ui/core"; 8 | 9 | import LoginCard from "./LoginCard"; 10 | import Welcome from "../Common/img/welcome.svg"; 11 | import { userSignInRequest } from "../store/actions/auth"; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | image: { 15 | width: "100%", 16 | pointerEvents: "none", 17 | userSelect: "none", 18 | }, 19 | })); 20 | 21 | function Login(props) { 22 | const classes = useStyles(); 23 | 24 | const { userSignInRequest } = props; 25 | 26 | return ( 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | } 45 | 46 | Login.propTypes = { 47 | userSignInRequest: PropTypes.func.isRequired, 48 | }; 49 | 50 | export default connect(null, { userSignInRequest })(Login); 51 | -------------------------------------------------------------------------------- /src/Login/LoginCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | import isEmpty from "lodash/isEmpty"; 5 | 6 | import Container from "@material-ui/core/Container"; 7 | import { makeStyles } from "@material-ui/core/styles"; 8 | import { 9 | Paper, 10 | withStyles, 11 | Grid, 12 | TextField, 13 | Button, 14 | FormControlLabel, 15 | Checkbox, 16 | Typography, 17 | InputAdornment, 18 | CircularProgress, 19 | Box, 20 | } from "@material-ui/core"; 21 | import { LockOutlined, PersonOutline } from "@material-ui/icons"; 22 | import ErrorMessage from "./ErrorMessage"; 23 | 24 | import ShopCastLogo from "../Common/img/shopcast.svg"; 25 | 26 | const useStyles = makeStyles((theme) => ({ 27 | paper: { 28 | padding: theme.spacing(1), 29 | // boxShadow: '0 0 1px 0 rgba(0,0,0,.22)', 30 | boxShadow: "0 0 11px #eaf0f6", 31 | }, 32 | margin: { 33 | margin: theme.spacing(2), 34 | }, 35 | title: { 36 | fontFamily: "ApercuMedium", 37 | marginBottom: theme.spacing(6), 38 | paddingTop: "20vh", 39 | }, 40 | textfield: { 41 | marginBottom: theme.spacing(4), 42 | }, 43 | password: { 44 | marginBottom: theme.spacing(1), 45 | }, 46 | inactive: { 47 | color: "#757575", 48 | }, 49 | active: { 50 | color: "#2196F3", 51 | }, 52 | button: { 53 | marginTop: theme.spacing(4), 54 | }, 55 | formControlLabel: { 56 | fontSize: "0.875rem", 57 | }, 58 | logo: { 59 | width: "128px", 60 | userSelect: "none", 61 | pointerEvents: "none", 62 | }, 63 | })); 64 | 65 | const Login = (props) => { 66 | const classes = useStyles(); 67 | 68 | // Misc UI indicators 69 | const [username, setUsername] = React.useState(false); 70 | const [password, setPassword] = React.useState(false); 71 | const [submit, setSubmit] = React.useState(false); 72 | 73 | // Values 74 | const [creds, setCreds] = React.useState({ 75 | username: "name@company.com", 76 | password: "", 77 | }); 78 | const [errors, setErrors] = React.useState({ 79 | username: false, 80 | password: false, 81 | }); 82 | 83 | const onSubmit = () => { 84 | setSubmit(true); 85 | 86 | if (!isEmpty(creds.username) && !isEmpty(creds.password)) { 87 | props.userSignInRequest(creds); 88 | } else { 89 | setSubmit(false); 90 | if (isEmpty(creds.username)) { 91 | setErrors({ 92 | username: true, 93 | }); 94 | } 95 | if (isEmpty(creds.password)) { 96 | setErrors({ 97 | ...errors, 98 | password: true, 99 | }); 100 | } 101 | } 102 | }; 103 | 104 | const ShowErrorMessage = () => { 105 | setSubmit(false); 106 | return ( 107 | 111 | ); 112 | }; 113 | 114 | return ( 115 | 116 | 117 |
118 | 119 | 124 | 125 | 126 | Welcome back! 127 | 128 | 138 | 141 | 142 | ), 143 | }} 144 | onFocus={() => setUsername(true)} 145 | onBlur={() => setUsername(false)} 146 | onChange={(e) => setCreds({ ...creds, username: e.target.value })} 147 | autoComplete="off" 148 | disabled={submit} 149 | error={errors.username} 150 | fullWidth 151 | autoFocus 152 | required 153 | /> 154 | 164 | 167 | 168 | ), 169 | }} 170 | onFocus={() => setPassword(true)} 171 | onBlur={() => setPassword(false)} 172 | onChange={(e) => setCreds({ ...creds, password: e.target.value })} 173 | autoComplete="off" 174 | disabled={submit} 175 | error={errors.password} 176 | fullWidth 177 | required 178 | /> 179 | 180 | 181 | } 183 | label={ 184 | 185 | Remember Me 186 | 187 | } 188 | /> 189 | 190 | 191 | 201 | 202 | 203 | 204 | 222 |
223 | {props.errorMessage.message ? : null} 224 |
225 |
226 |
227 |
228 |
229 | ); 230 | }; 231 | 232 | Login.propTypes = { 233 | userSignInRequest: PropTypes.func.isRequired, 234 | }; 235 | 236 | function mapStateToProps(state) { 237 | return { 238 | errorMessage: state.auth.error, 239 | }; 240 | } 241 | 242 | export default connect(mapStateToProps)(Login); 243 | -------------------------------------------------------------------------------- /src/Orders/DraftOrder.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | 6 | import { Container, Paper, Typography, Button } from "@material-ui/core/"; 7 | 8 | import PageTitle from "../Common/PageTitle"; 9 | import WarningModal from "../Common/WarningModal"; 10 | import TableBuilder from "../Common/TableBuilder"; 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | root: { 14 | boxShadow: "0 0 11px #eaf0f6", 15 | borderRadius: "4px", 16 | overflow: "hidden", 17 | border: "1px solid #eaf0f6", 18 | }, 19 | title: { 20 | fontFamily: "ApercuMedium", 21 | }, 22 | })); 23 | 24 | const DraftOrder = (props) => { 25 | const classes = useStyles(); 26 | const [open, setOpen] = React.useState(false); 27 | 28 | const products = useSelector((state) => state.product.products); 29 | 30 | const handleOpen = () => { 31 | setOpen(true); 32 | }; 33 | 34 | const quitWarning = () => { 35 | handleOpen(true); 36 | }; 37 | 38 | const quit = () => { 39 | props.setPageControl({ 40 | manage: false, 41 | orderDetails: null, 42 | draftOrder: false, 43 | root: true, 44 | }); 45 | }; 46 | 47 | const modalDetails = { 48 | animationKey: "alertCircle", 49 | title: "Abandon Draft Order", 50 | subtitle: [ 51 | "Leaving will return you to the Orders page. You will lose all progress on creating your Draft Order.", 52 | "You will have to start over to create new Draft Order.", 53 | ], 54 | action: "Abandon", 55 | }; 56 | 57 | return ( 58 | 59 | 64 | 70 | 71 | Order Details 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | DraftOrder.defaultProps = { 79 | products: [ 80 | { 81 | id: 1, 82 | name: "name", 83 | price: 1, 84 | pictureUrl: "url", 85 | description: "desc", 86 | brand: "brand", 87 | category: "cat", 88 | colour: "colour", 89 | }, 90 | ], 91 | }; 92 | 93 | DraftOrder.propTypes = { 94 | products: PropTypes.array.isRequired, 95 | }; 96 | 97 | export default DraftOrder; 98 | -------------------------------------------------------------------------------- /src/Orders/Manage/Manage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { 4 | makeStyles, 5 | Container, 6 | Paper, 7 | Typography, 8 | Stepper, 9 | Step, 10 | StepLabel, 11 | Button, 12 | } from "@material-ui/core/"; 13 | 14 | import PageTitle from "../../Common/PageTitle"; 15 | import New from "./New"; 16 | 17 | const useStyles = makeStyles((theme) => ({ 18 | root: { 19 | width: "100%", 20 | marginTop: theme.spacing(3), 21 | boxShadow: "0 0 11px #eaf0f6", 22 | borderRadius: "4px", 23 | }, 24 | paper: { 25 | boxShadow: "0 0 1px 0 rgba(0,0,0,.22)", 26 | }, 27 | backButton: { 28 | marginRight: theme.spacing(1), 29 | }, 30 | instructions: { 31 | marginTop: theme.spacing(1), 32 | marginBottom: theme.spacing(1), 33 | }, 34 | })); 35 | 36 | function getSteps() { 37 | return [ 38 | "New", 39 | "Active", 40 | "Inventory", 41 | "Package", 42 | "Approve", 43 | "Fulfilled", 44 | "Delivered", 45 | ]; 46 | } 47 | 48 | export default function Manage(props) { 49 | const classes = useStyles(); 50 | 51 | const [activeStep, setActiveStep] = React.useState(0); 52 | const steps = getSteps(); 53 | 54 | const handleNext = () => { 55 | setActiveStep((prevActiveStep) => prevActiveStep + 1); 56 | }; 57 | 58 | const handleBack = () => { 59 | setActiveStep((prevActiveStep) => prevActiveStep - 1); 60 | }; 61 | 62 | const handleReset = () => { 63 | setActiveStep(0); 64 | }; 65 | 66 | const quit = () => { 67 | props.setPageControl({ 68 | manage: false, 69 | root: true, 70 | draftOrder: false, 71 | }); 72 | }; 73 | 74 | function getStepContent(stepIndex) { 75 | switch (stepIndex) { 76 | case 0: 77 | return ( 78 | 83 | ); 84 | case 1: 85 | return "What is an ad group anyways?"; 86 | case 2: 87 | return "This is the bit I really care about!"; 88 | default: 89 | return "Unknown stepIndex"; 90 | } 91 | } 92 | 93 | return ( 94 | 95 | 96 | 101 | 102 | 103 | {steps.map((label) => ( 104 | 105 | {label} 106 | 107 | ))} 108 | 109 | 110 | 111 |
112 | {activeStep === steps.length ? ( 113 |
114 | 115 | All steps completed 116 | 117 | 118 |
119 | ) : ( 120 |
{getStepContent(activeStep)}
121 | )} 122 |
123 |
124 |
125 |
126 | ); 127 | } 128 | -------------------------------------------------------------------------------- /src/Orders/Manage/Metadata.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import faker from "faker"; 3 | 4 | import { Typography, TextField, Box, makeStyles } from "@material-ui/core"; 5 | 6 | import Autocomplete from "@material-ui/lab/Autocomplete"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | textfield: { 10 | marginRight: theme.spacing(2), 11 | marginBottom: theme.spacing(3), 12 | }, 13 | title: { 14 | fontFamily: "ApercuMedium", 15 | color: theme.palette.action.active, 16 | marginBottom: theme.spacing(2), 17 | }, 18 | })); 19 | 20 | export default function Metadata(props) { 21 | const classes = useStyles(); 22 | 23 | // TODO: Add state to 24 | 25 | const testProps = { 26 | ...props.pageControl.orderDetails, 27 | assignedTo: `${faker.name.firstName()} ${faker.name.lastName()}`, 28 | // inventorySignoff: `${faker.name.firstName()} ${faker.name.lastName()}`, 29 | packingSignoff: `${faker.name.firstName()} ${faker.name.lastName()}`, 30 | }; 31 | 32 | const { 33 | orderId, 34 | created, 35 | assignedTo, 36 | inventorySignoff, 37 | packingSignoff, 38 | } = testProps; 39 | 40 | const managers = [ 41 | { name: "Boris Chan", email: "boris@shopcast.com" }, 42 | { name: "Conan O'Brien", email: "conan@shopcast.com" }, 43 | { name: "Linus Sebastian", email: "linus@shopcast.com" }, 44 | ]; 45 | 46 | return ( 47 | 48 | 49 | Order Metadata 50 | 51 | 52 | 63 | 74 | 85 | {assignedTo && ( 86 | 97 | )} 98 |
99 | {/* {inventorySignoff && ( 100 | manager.name)} 103 | renderInput={(params) => ( 104 | 113 | )} 114 | /> 115 | )} 116 | {packingSignoff && ( 117 | manager.name)} 120 | renderInput={(params) => ( 121 | 130 | )} 131 | /> 132 | )} */} 133 |
134 |
135 |
136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /src/Orders/Manage/New.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import { 4 | makeStyles, 5 | Container, 6 | Typography, 7 | TextField, 8 | Box, 9 | Button, 10 | IconButton, 11 | Popover, 12 | } from "@material-ui/core"; 13 | 14 | import DoneIcon from "@material-ui/icons/Done"; 15 | import PostAdd from "@material-ui/icons/PostAdd"; 16 | 17 | import Metadata from "./Metadata"; 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | textfield: { 21 | marginRight: theme.spacing(2), 22 | marginBottom: theme.spacing(3), 23 | }, 24 | root: { 25 | padding: theme.spacing(4), 26 | }, 27 | title: { 28 | fontFamily: "ApercuMedium", 29 | color: theme.palette.action.active, 30 | marginBottom: theme.spacing(2), 31 | }, 32 | subtitle: { 33 | fontFamily: "ApercuMedium", 34 | color: theme.palette.action.active, 35 | marginBottom: theme.spacing(2), 36 | marginTop: theme.spacing(2), 37 | }, 38 | button: { 39 | boxShadow: "none", 40 | fontFamily: "ApercuMedium", 41 | }, 42 | rightIcon: { 43 | marginLeft: theme.spacing(1), 44 | }, 45 | active: { 46 | color: theme.palette.primary.main, 47 | }, 48 | popover: { 49 | padding: theme.spacing(2), 50 | }, 51 | })); 52 | 53 | export default function New(props) { 54 | const classes = useStyles(); 55 | 56 | // TODO: Import the schema instead of manually deconstructing 57 | const { 58 | orderId, 59 | created, 60 | firstName, 61 | lastName, 62 | email, 63 | address, 64 | city, 65 | country, 66 | zip, 67 | fulfillment, 68 | total, 69 | status, 70 | updated, 71 | } = props.pageControl.orderDetails; 72 | 73 | const handleNext = () => { 74 | // Send RESTful call to update Order state on backend. 75 | props.handleNext(); 76 | }; 77 | 78 | return ( 79 | 80 |
81 | 82 | 83 | Customer Information 84 | 85 | 86 | 96 | 106 | 117 | 118 | 119 | 130 | 141 | 142 | 143 | 153 | 163 | 164 | 165 | 174 | 175 | 176 |
177 | ); 178 | } 179 | -------------------------------------------------------------------------------- /src/Orders/Orders.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route } from "react-router-dom"; 3 | import faker from "faker"; 4 | import { connect } from "react-redux"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import { fetchProducts } from "../store/actions/products"; 7 | 8 | import { 9 | IconButton, 10 | Button, 11 | Container, 12 | Box, 13 | Tooltip, 14 | } from "@material-ui/core/"; 15 | import RefreshIcon from "@material-ui/icons/Refresh"; 16 | import SortIcon from "@material-ui/icons/Sort"; 17 | import SearchIcon from "@material-ui/icons/Search"; 18 | import PlaylistAddIcon from "@material-ui/icons/PlaylistAdd"; 19 | import blue from "@material-ui/core/colors/blue"; 20 | 21 | import OrdersTable from "./OrdersTable"; 22 | import Manage from "./Manage/Manage"; 23 | import DraftOrder from "./DraftOrder"; 24 | import PageTitle from "./../Common/PageTitle"; 25 | 26 | const drawerWidth = 210; 27 | 28 | const useStyles = makeStyles((theme) => ({ 29 | root: { 30 | display: "flex", 31 | zIndex: 0, 32 | }, 33 | drawer: { 34 | width: drawerWidth, 35 | flexShrink: 0, 36 | whiteSpace: "nowrap", 37 | }, 38 | paper: { 39 | boxShadow: "0 0 1px 0 rgba(0,0,0,.22)", 40 | }, 41 | toolbar: { 42 | boxShadow: "0 0 11px #eaf0f6", 43 | display: "inline-block", 44 | marginBottom: theme.spacing(1), 45 | width: "100%", 46 | }, 47 | button: { 48 | margin: theme.spacing(1), 49 | }, 50 | action: { 51 | marginLeft: "auto", 52 | marginTop: "0.8rem", 53 | marginRight: theme.spacing(2), 54 | }, 55 | lastUpdated: { 56 | marginTop: theme.spacing(2), 57 | padding: 0, 58 | color: "rgb(112, 117, 122)", 59 | }, 60 | iconButton: { 61 | margin: theme.spacing(1), 62 | }, 63 | button: { 64 | backgroundColor: blue[100], 65 | // margin: theme.spacing(1), 66 | borderRadius: "30px", 67 | "&:hover": { 68 | backgroundColor: theme.palette.primary.main, 69 | "& $icon": { 70 | color: "white", 71 | }, 72 | }, 73 | }, 74 | icon: { 75 | color: theme.palette.primary.main, 76 | }, 77 | })); 78 | 79 | // TODO: Organize scheme to its own repository. 80 | 81 | function createData( 82 | orderId, 83 | created, 84 | firstName, 85 | lastName, 86 | email, 87 | address, 88 | city, 89 | country, 90 | zip, 91 | fulfillment, 92 | total, 93 | status, 94 | updated 95 | ) { 96 | return { 97 | orderId, 98 | created, 99 | firstName, 100 | lastName, 101 | email, 102 | address, 103 | city, 104 | country, 105 | zip, 106 | fulfillment, 107 | total, 108 | status, 109 | updated, 110 | }; 111 | } 112 | 113 | // TODO: State should be retrieved from here. 114 | 115 | const populate = (n) => { 116 | const data = []; 117 | const set = new Set(); 118 | for (let i = 0; i < n; i++) { 119 | let random = faker.random.number(100); 120 | 121 | while (set.has(random)) { 122 | random = faker.random.number(100); 123 | } 124 | 125 | set.add(random); 126 | 127 | data.push( 128 | createData( 129 | random, 130 | faker.date.recent(7).toLocaleDateString(), 131 | faker.name.firstName(), 132 | faker.name.lastName(), 133 | faker.internet.email(), 134 | faker.address.streetAddress(), 135 | faker.address.city(), 136 | faker.address.country(), 137 | faker.address.zipCode(), 138 | "Processing", 139 | faker.commerce.price(), 140 | "Paid", 141 | "Today" 142 | ) 143 | ); 144 | } 145 | 146 | return data; 147 | }; 148 | 149 | const ordersData = populate(8); 150 | 151 | const Orders = (props) => { 152 | const classes = useStyles(); 153 | 154 | const [pageControl, setPageControl] = React.useState({ 155 | manage: false, 156 | orderDetails: null, 157 | draftOrder: false, 158 | root: true, 159 | }); 160 | 161 | const openDraftOrder = () => { 162 | setPageControl({ 163 | manage: false, 164 | orderDetails: null, 165 | draftOrder: true, 166 | root: false, 167 | }); 168 | }; 169 | 170 | const [lastUpdatedTime, setLastUpdatedTime] = React.useState("N/A"); 171 | 172 | React.useEffect(() => { 173 | props.dispatch(fetchProducts()); 174 | setLastUpdatedTime(`${new Date().toLocaleString()}`); 175 | }, []); 176 | 177 | const OrdersMain = (props) => { 178 | return ( 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 203 | 204 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | ); 218 | }; 219 | 220 | // TODO: Need to refactor. This is a workaround for Router (since its connected to Tabs, its difficult to hack) 221 | 222 | return ( 223 | 224 | {pageControl.manage && ( 225 | 226 | )} 227 | {pageControl.root && } 228 | {pageControl.draftOrder && ( 229 | 230 | )} 231 | 232 | ); 233 | }; 234 | 235 | function mapStateToProps(state) { 236 | return { 237 | products: state.product.products, 238 | }; 239 | } 240 | 241 | export default connect(mapStateToProps, null)(Orders); 242 | -------------------------------------------------------------------------------- /src/Orders/OrdersStats.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Paper from '@material-ui/core/Paper'; 4 | import Grid from '@material-ui/core/Grid'; 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | root: { 8 | flexGrow: 1, 9 | paddingTop: '1em', 10 | }, 11 | paper: { 12 | padding: theme.spacing(2), 13 | textAlign: 'center', 14 | color: theme.palette.text.secondary, 15 | }, 16 | })); 17 | 18 | export default function OrderStats() { 19 | const classes = useStyles(); 20 | 21 | return ( 22 |
23 | 24 | 25 | Active Orders 26 | 27 | 28 | Unfulfilled 29 | 30 | 31 |
32 | ); 33 | } -------------------------------------------------------------------------------- /src/Orders/OrdersTable.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | import Table from "@material-ui/core/Table"; 6 | import TableBody from "@material-ui/core/TableBody"; 7 | import TableCell from "@material-ui/core/TableCell"; 8 | import TableHead from "@material-ui/core/TableHead"; 9 | import TableRow from "@material-ui/core/TableRow"; 10 | import Paper from "@material-ui/core/Paper"; 11 | import Chip from "@material-ui/core/Chip"; 12 | import blue from "@material-ui/core/colors/blue"; 13 | import grey from "@material-ui/core/colors/grey"; 14 | 15 | import { getThemeProps } from "@material-ui/styles"; 16 | import DoneIcon from "@material-ui/icons/Done"; 17 | import ErrorIcon from "@material-ui/icons/Error"; 18 | import CachedIcon from "@material-ui/icons/Cached"; 19 | import ConfirmedIcon from "@material-ui/icons/AssignmentLate"; 20 | import ShippingIcon from "@material-ui/icons/LocalShipping"; 21 | import PackingIcon from "@material-ui/icons/MoveToInbox"; 22 | import UnfoldMoreIcon from "@material-ui/icons/UnfoldMore"; 23 | import KeyboardArrowRightIcon from "@material-ui/icons/KeyboardArrowRight"; 24 | 25 | import { Typography, Box, IconButton } from "@material-ui/core"; 26 | import { Pagination } from "@material-ui/lab"; 27 | 28 | const useStyles = makeStyles((theme) => ({ 29 | root: { 30 | boxShadow: "0 0 11px #eaf0f6", 31 | borderRadius: "4px", 32 | overflow: "hidden", 33 | border: "1px solid #eaf0f6", 34 | }, 35 | table: { 36 | minWidth: 650, 37 | }, 38 | tableHead: { 39 | fontFamily: "ApercuMedium", 40 | fontSize: "0.875rem", 41 | color: "#525f7f", 42 | paddingLeft: theme.spacing(2), 43 | paddingTop: theme.spacing(2), 44 | paddingBottom: theme.spacing(2), 45 | }, 46 | tableHeadCell: { 47 | padding: theme.spacing(1), 48 | }, 49 | tableHeadRow: { 50 | backgroundColor: "#fff", 51 | borderColor: "#fff", 52 | borderStyle: "solid", 53 | borderLeftWidth: "3px", 54 | }, 55 | tableFoot: { 56 | fontFamily: "ApercuMedium", 57 | fontSize: "0.875rem", 58 | color: "#525f7f", 59 | }, 60 | button: { 61 | margin: theme.spacing(1), 62 | }, 63 | primaryButton: { 64 | backgroundColor: "#2196f3", 65 | }, 66 | input: { 67 | display: "none", 68 | }, 69 | tableRow: { 70 | borderColor: "#fff", 71 | borderStyle: "solid", 72 | borderLeftWidth: "3px", 73 | borderBottomWidth: "0px", 74 | borderTopWidth: "0px", 75 | borderRightWidth: "3px", 76 | "&:hover": { 77 | borderColor: theme.palette.primary.light, 78 | borderStyle: "solid", 79 | borderLeftWidth: "3px", 80 | backgroundColor: blue[50], 81 | borderRightColor: blue[50], 82 | cursor: "pointer", 83 | }, 84 | }, 85 | paidChip: { 86 | backgroundColor: "#66BB6A", 87 | color: "#fff", 88 | }, 89 | unfulfilledChip: { 90 | backgroundColor: "#F44336", 91 | color: "#fff", 92 | }, 93 | active: { 94 | color: theme.palette.primary.main, 95 | }, 96 | even: { 97 | backgroundColor: "#fff", 98 | }, 99 | })); 100 | 101 | export default function SimpleTable(props) { 102 | const classes = useStyles(); 103 | 104 | const [lastUpdatedTime, setLastUpdatedTime] = React.useState("N/A"); 105 | React.useEffect(() => { 106 | // props.dispatch(fetchProducts()); 107 | setLastUpdatedTime(`${new Date().toLocaleString()}`); 108 | }, []); 109 | 110 | const [data, setData] = React.useState(props.orders); 111 | 112 | /* -1: unsorted/unused 113 | 0: is NOT ascending 114 | 1: is ascending 115 | */ 116 | const [sortData, setSortData] = React.useState({ 117 | id: -1, 118 | }); 119 | 120 | const sortById = () => { 121 | const dataset = [...data]; 122 | 123 | if (sortData.id < 1) { 124 | console.log("Unsorted. Sorting By Ascending"); 125 | dataset.sort(function (a, b) { 126 | return a.orderId - b.orderId; 127 | }); 128 | setSortData({ 129 | ...sortData, 130 | id: 1, 131 | }); 132 | } else { 133 | console.log("Sorted. Sorting By Descending"); 134 | dataset.reverse(); 135 | setSortData({ 136 | ...sortData, 137 | id: 0, 138 | }); 139 | } 140 | setData(dataset); 141 | }; 142 | 143 | // Placeholder hooks for pagination. 144 | const [page, setPage] = React.useState(1); 145 | const handleChange = (event, value) => { 146 | setPage(value); 147 | }; 148 | 149 | function StatusChip(props) { 150 | if (props.status === "Paid") { 151 | return Paid; 152 | } else { 153 | return ( 154 | 158 | ); 159 | } 160 | } 161 | 162 | function Fulfillment(props) { 163 | switch (props.fulfillment) { 164 | case "Processing": 165 | return ( 166 | } 168 | label={props.fulfillment} 169 | style={{ 170 | color: "#263238", 171 | backgroundColor: "#ECEFF1", 172 | paddingLeft: 2, 173 | }} 174 | /> 175 | ); 176 | 177 | case "Confirmed": 178 | return ( 179 | 182 | } 183 | label={props.fulfillment} 184 | style={{ 185 | color: "#FF6F00", 186 | backgroundColor: "#FFECB3", 187 | paddingLeft: 2, 188 | }} 189 | /> 190 | ); 191 | 192 | case "Packing": 193 | return ( 194 | } 196 | label={props.fulfillment} 197 | style={{ 198 | color: "#33691E", 199 | backgroundColor: "#DCEDC8", 200 | paddingLeft: 2, 201 | }} 202 | /> 203 | ); 204 | 205 | case "Shipped": 206 | return ( 207 | 210 | } 211 | label={props.fulfillment} 212 | style={{ 213 | color: "#0D47A1", 214 | backgroundColor: "#BBDEFB", 215 | paddingLeft: 2, 216 | }} 217 | /> 218 | ); 219 | 220 | case "Unfulfilled": 221 | return ( 222 | } 224 | label={props.fulfillment} 225 | style={{ 226 | color: "#b71c1c", 227 | backgroundColor: "#ffcdd2", 228 | paddingLeft: 2, 229 | }} 230 | /> 231 | ); 232 | 233 | default: 234 | return ( 235 | } 237 | label={props.fulfillment} 238 | /> 239 | ); 240 | } 241 | } 242 | 243 | return ( 244 | 245 | 246 | 247 | 248 | 249 | 250 | 255 | 256 | Order ID 257 | 258 | 259 | -1 && classes.active)} 262 | /> 263 | 264 | 265 | 266 | 267 | Created 268 | 269 | 270 | Customer 271 | 272 | 273 | Email 274 | 275 | 276 | Fulfillment 277 | 278 | 279 | Total 280 | 281 | 282 | Status 283 | 284 | 285 | Last Updated 286 | 287 | 288 | 289 | 290 | 291 | {data.map((row, index) => ( 292 | 296 | props.pageControl({ 297 | manage: true, 298 | orderDetails: data[index], 299 | root: false, 300 | purchaseOrder: false, 301 | }) 302 | } 303 | > 304 | 305 | {row.orderId} 306 | 307 | {row.created} 308 | {`${row.firstName} ${row.lastName}`} 309 | {row.email} 310 | 311 | 312 | 313 | {row.total} 314 | 315 | 316 | 317 | {row.updated} 318 | 319 | 320 | 321 | 322 | 323 | 324 | ))} 325 | 326 |
327 |
328 |
329 | 330 | 331 | {/* 332 | 1-{data.length} of {Math.floor(Math.random() * 100)} results 333 | */} 334 | 335 | 336 | 337 | 338 | Orders up to date. Last retrieved at {lastUpdatedTime} 339 | 340 | 341 | 342 |
343 |
344 | ); 345 | } 346 | -------------------------------------------------------------------------------- /src/Orders/Search.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import MenuItem from '@material-ui/core/MenuItem'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import Button from '@material-ui/core/Button'; 6 | import Box from '@material-ui/core/Box'; 7 | import SearchIcon from '@material-ui/icons/Search'; 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | container: { 11 | display: 'flex', 12 | flexWrap: 'wrap', 13 | }, 14 | textField: { 15 | marginLeft: theme.spacing(1), 16 | width: '275px' 17 | }, 18 | typeField: { 19 | marginRight: theme.spacing(1), 20 | width: '175px' 21 | }, 22 | dense: { 23 | marginTop: theme.spacing(2), 24 | }, 25 | menu: { 26 | width: 200, 27 | }, 28 | rightIcon: { 29 | marginLeft: theme.spacing(1), 30 | }, 31 | button: { 32 | boxShadow: 'none', 33 | } 34 | })); 35 | 36 | 37 | 38 | export default function Search() { 39 | const classes = useStyles(); 40 | const [values, setValues] = React.useState({ 41 | type: '', 42 | }); 43 | 44 | const [queryValue, setQueryValue] = React.useState(''); 45 | 46 | const attributes = [ 47 | { 48 | value: 'Order ID', 49 | label: 'Order ID', 50 | }, 51 | { 52 | value: 'Email', 53 | label: 'Email', 54 | }, 55 | ]; 56 | 57 | function handleChange(event) { 58 | setValues(oldValues => ({ 59 | type: event.target.value, 60 | })); 61 | } 62 | 63 | function handleQueryValue(e) { 64 | setQueryValue(e.target.value); 65 | } 66 | 67 | return ( 68 |
69 |

Query Orders

70 | 86 | {attributes.map(option => ( 87 | 88 | {option.label} 89 | 90 | ))} 91 | 92 | 101 | 102 | 106 | 107 | 108 |
109 | ); 110 | } -------------------------------------------------------------------------------- /src/Orders/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles, useTheme } from '@material-ui/core/styles'; 3 | import Drawer from '@material-ui/core/Drawer'; 4 | import List from '@material-ui/core/List'; 5 | import Divider from '@material-ui/core/Divider'; 6 | import ListItem from '@material-ui/core/ListItem'; 7 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 8 | import ListItemText from '@material-ui/core/ListItemText'; 9 | import InboxIcon from '@material-ui/icons/MoveToInbox'; 10 | import MailIcon from '@material-ui/icons/Mail'; 11 | import EditIcon from '@material-ui/icons/Edit'; 12 | import SearchIcon from '@material-ui/icons/Search'; 13 | import RefreshIcon from '@material-ui/icons/Refresh'; 14 | import Typography from '@material-ui/core/Typography'; 15 | import Modal from '@material-ui/core/Modal'; 16 | import Backdrop from '@material-ui/core/Backdrop'; 17 | import Fade from '@material-ui/core/Fade'; 18 | import blue from "@material-ui/core/colors/blue"; 19 | 20 | import Search from './Search'; 21 | 22 | const drawerWidth = 120; 23 | 24 | const useStyles = makeStyles(theme => ({ 25 | root: { 26 | display: 'flex', 27 | zIndex: 0, 28 | backgroundColor: blue[500], 29 | }, 30 | appBarShift: { 31 | marginLeft: drawerWidth, 32 | width: `calc(100% - ${drawerWidth}px)`, 33 | transition: theme.transitions.create(['width', 'margin'], { 34 | easing: theme.transitions.easing.sharp, 35 | duration: theme.transitions.duration.enteringScreen, 36 | }), 37 | }, 38 | menuButton: { 39 | marginRight: 36, 40 | }, 41 | hide: { 42 | display: 'none', 43 | }, 44 | drawer: { 45 | width: drawerWidth, 46 | flexShrink: 0, 47 | whiteSpace: 'nowrap', 48 | }, 49 | toolbar: { 50 | display: 'flex', 51 | alignItems: 'center', 52 | justifyContent: 'flex-end', 53 | // padding: theme.spacing(0, 1), 54 | ...theme.mixins.toolbar, 55 | }, 56 | content: { 57 | flexGrow: 1, 58 | padding: theme.spacing(3), 59 | }, 60 | icon: { 61 | minWidth: 35 62 | }, 63 | text: { 64 | color: 'rgba(0, 0, 0, 0.54)', 65 | }, 66 | modal: { 67 | display: 'flex', 68 | alignItems: 'center', 69 | justifyContent: 'center', 70 | }, 71 | paper: { 72 | padding: theme.spacing(2, 4, 3), 73 | backgroundColor: '#fff' 74 | }, 75 | })); 76 | 77 | export default function MiniDrawer() { 78 | const classes = useStyles(); 79 | 80 | const [open, setOpen] = React.useState(false); 81 | 82 | const handleOpen = () => { 83 | setOpen(true); 84 | }; 85 | 86 | const handleClose = () => { 87 | setOpen(false); 88 | }; 89 | 90 | return ( 91 |
92 | 93 |
94 | 95 | 96 | 97 | 98 | 99 | Search } /> 102 | 103 | 104 | 105 | 106 | 107 | 108 | Update } /> 111 | 112 | 113 | 114 | 115 | 116 | 117 | Message } /> 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | Archive } /> 132 | 133 | 134 | 135 | 136 | 137 | 138 | Refresh } /> 141 | 142 | 143 |
144 | 145 | 157 | 158 |
159 | 160 |
161 |
162 |
163 | 164 |
165 | ); 166 | } -------------------------------------------------------------------------------- /src/Settings/SettingTabs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Tabs from '@material-ui/core/Tabs'; 5 | import Tab from '@material-ui/core/Tab'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import Box from '@material-ui/core/Box'; 8 | 9 | 10 | import TabButton from './TabButton'; 11 | import Account from './Views/Account'; 12 | import Password from './Views/Password'; 13 | import Notifications from './Views/Notifications'; 14 | import UserGroups from './Views/UserGroups'; 15 | 16 | function TabPanel(props) { 17 | const { children, value, index, ...other } = props; 18 | 19 | return ( 20 | 31 | ); 32 | } 33 | 34 | TabPanel.propTypes = { 35 | children: PropTypes.node, 36 | index: PropTypes.any.isRequired, 37 | value: PropTypes.any.isRequired, 38 | }; 39 | 40 | function a11yProps(index) { 41 | return { 42 | id: `vertical-tab-${index}`, 43 | 'aria-controls': `vertical-tabpanel-${index}`, 44 | }; 45 | } 46 | 47 | const useStyles = makeStyles(theme => ({ 48 | root: { 49 | flexGrow: 1, 50 | backgroundColor: theme.palette.background.paper, 51 | display: 'flex', 52 | height: 500, 53 | }, 54 | tabs: { 55 | borderRight: `1px solid rgba(0,0,0,.08)`, 56 | textAlign: 'left' 57 | }, 58 | leftIcon: { 59 | marginRight: theme.spacing(1), 60 | }, 61 | })); 62 | 63 | 64 | export default function VerticalTabs() { 65 | const classes = useStyles(); 66 | const [value, setValue] = React.useState(0); 67 | 68 | function handleChange(event, newValue) { 69 | setValue(newValue); 70 | } 71 | 72 | return ( 73 |
74 | 84 | } {...a11yProps(0)} /> 85 | } {...a11yProps(1)} /> 86 | } {...a11yProps(2)} /> 87 | } {...a11yProps(3)} /> 88 | } {...a11yProps(4)} /> 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Help 104 | 105 |
106 | ); 107 | } -------------------------------------------------------------------------------- /src/Settings/Settings.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Container from '@material-ui/core/Container'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Paper from '@material-ui/core/Paper'; 6 | 7 | import SettingTabs from './SettingTabs'; 8 | import PageTitle from './../Common/PageTitle'; 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | paper: { 12 | // boxShadow: '0 0 1px 0 rgba(0,0,0,.22)', 13 | boxShadow: '0 0 11px #eaf0f6', 14 | borderRadius: '4px', 15 | overflow: 'hidden', 16 | marginTop: theme.spacing(4), 17 | }, 18 | title: { 19 | fontFamily: 'ApercuMedium', 20 | marginTop: theme.spacing(4), 21 | } 22 | })); 23 | 24 | export default function Settings() { 25 | 26 | const classes = useStyles(); 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | } -------------------------------------------------------------------------------- /src/Settings/TabButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Box from '@material-ui/core/Box'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import AccountIcon from '@material-ui/icons/AccountCircle'; 6 | import PasswordIcon from '@material-ui/icons/Lock'; 7 | import UserGroupsIcon from '@material-ui/icons/SupervisorAccount'; 8 | import NotificationsIcon from '@material-ui/icons/Notifications'; 9 | import HelpIcon from '@material-ui/icons/Help'; 10 | import Grid from '@material-ui/core/Grid'; 11 | 12 | const useStyles = makeStyles(theme => ({ 13 | button: { 14 | marginTop: '4px', 15 | fontSize: '1em', 16 | width: 175, 17 | marginLeft: theme.spacing(2), 18 | fontFamily: 'ApercuMedium', 19 | }, 20 | leftIcon: { 21 | marginRight: theme.spacing(2), 22 | }, 23 | text: { 24 | marginBottom: '5px', 25 | } 26 | })); 27 | 28 | export default function TabButton(props) { 29 | const classes = useStyles(); 30 | 31 | function GetIcon() { 32 | switch (props.icon) { 33 | case 'account': 34 | return (); 35 | case 'password': 36 | return (); 37 | case 'usergroups': 38 | return (); 39 | case 'notifications': 40 | return (); 41 | case 'help': 42 | return (); 43 | default: 44 | throw new SyntaxError('Invalid button type'); 45 | } 46 | } 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | { props.text } 55 | 56 | 57 | 58 | ) 59 | } -------------------------------------------------------------------------------- /src/Settings/Views/Account.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Grid from '@material-ui/core/Grid'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Edit from '@material-ui/icons/KeyboardArrowRight'; 6 | import Divider from '@material-ui/core/Divider'; 7 | import Modal from '@material-ui/core/Modal'; 8 | 9 | import Loading from './../../Common/Loading'; 10 | import FieldRow from './../../Common/FieldRow'; 11 | import FieldModal from './../../Common/FieldModal'; 12 | 13 | const useStyles = makeStyles(theme => ({ 14 | root: { 15 | flexGrow: 1, 16 | }, 17 | paper: { 18 | padding: theme.spacing(2), 19 | textAlign: 'center', 20 | color: theme.palette.text.secondary, 21 | }, 22 | rightIcon: { 23 | fontSize: '1em', 24 | }, 25 | textField: { 26 | marginLeft: theme.spacing(1), 27 | marginRight: theme.spacing(1), 28 | }, 29 | label: { 30 | letterSpacing: '.07272727em', 31 | fontSize: '.6875rem', 32 | fontWeight: 500, 33 | lineHeight: '1rem', 34 | textTransform: 'uppercase', 35 | color: '#5f6368', 36 | marginLeft: '10px', 37 | }, 38 | row: { 39 | // margin: theme.spacing(1), 40 | paddingTop: theme.spacing(1), 41 | '&:hover': { 42 | backgroundColor: '#f5f5f5', 43 | cursor: 'pointer' 44 | } 45 | }, 46 | modal: { 47 | display: 'flex', 48 | alignItems: 'center', 49 | justifyContent: 'center', 50 | }, 51 | })); 52 | 53 | function a() { 54 | return alert('hi'); 55 | } 56 | 57 | export default function Account() { 58 | const classes = useStyles(); 59 | 60 | const [loading, setLoading] = React.useState(true); 61 | 62 | const [fieldModal, setFieldModal] = React.useState({ 63 | open: false, 64 | field: { 65 | label: null, 66 | value: null 67 | } 68 | }); 69 | 70 | const showFieldModal = (field) => { 71 | setFieldModal({ 72 | open: true, 73 | field, 74 | }); 75 | }; 76 | 77 | const handleClose = () => { 78 | setFieldModal({ 79 | ...fieldModal, 80 | open: false 81 | }); 82 | }; 83 | 84 | setTimeout(() => { 85 | setLoading(false); 86 | }, 1000); 87 | 88 | function Content() { 89 | return ( 90 | 91 | 92 | Profile 93 | 94 | 95 | 96 | 97 | 98 | 109 |
110 | 111 |
112 |
113 | 114 |
115 | ); 116 | } 117 | 118 | 119 | return ( 120 |
121 | {loading ? ( 122 | 123 | ) : ( 124 | 125 | )} 126 |
127 | ) 128 | } -------------------------------------------------------------------------------- /src/Settings/Views/Notifications.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Grid from '@material-ui/core/Grid'; 5 | import Paper from '@material-ui/core/Paper'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import Edit from '@material-ui/icons/KeyboardArrowRight'; 8 | import TextField from '@material-ui/core/TextField'; 9 | import Divider from '@material-ui/core/Divider'; 10 | import Checkbox from '@material-ui/core/Checkbox'; 11 | 12 | const useStyles = makeStyles(theme => ({ 13 | root: { 14 | flexGrow: 1, 15 | }, 16 | paper: { 17 | padding: theme.spacing(2), 18 | textAlign: 'center', 19 | color: theme.palette.text.secondary, 20 | }, 21 | rightIcon: { 22 | fontSize: '1em', 23 | }, 24 | textField: { 25 | marginLeft: theme.spacing(1), 26 | marginRight: theme.spacing(1), 27 | }, 28 | label: { 29 | letterSpacing: '.07272727em', 30 | fontSize: '.6875rem', 31 | fontWeight: 500, 32 | lineHeight: '1rem', 33 | textTransform: 'uppercase', 34 | color: '#5f6368', 35 | marginLeft: '10px', 36 | }, 37 | row: { 38 | // margin: theme.spacing(1), 39 | paddingTop: theme.spacing(1), 40 | '&:hover': { 41 | backgroundColor: '#f5f5f5', 42 | cursor: 'pointer' 43 | } 44 | }, 45 | checkbox: { 46 | marginLeft: theme.spacing(1), 47 | marginBottom: theme.spacing(1) 48 | } 49 | })); 50 | 51 | function a() { 52 | return alert('hi'); 53 | } 54 | 55 | export default function Notifications() { 56 | const classes = useStyles(); 57 | 58 | const [state, setState] = React.useState({ 59 | newProductEmail: true, 60 | newProductMobile: false, 61 | }); 62 | 63 | const handleChange = name => event => { 64 | setState({ ...state, [name]: event.target.checked }); 65 | }; 66 | 67 | return ( 68 |
69 | 70 | Notifications 71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | Email 80 | 81 | 82 | 83 | 84 | Mobile 85 | 86 | 87 | 88 | 89 | 90 |
91 | 92 |
93 | 94 | 95 | 96 | New Product Is Created 97 | 98 | 99 | 100 | 110 | 111 | 112 | 122 | 123 | 124 | 125 | 126 | 127 |
128 | 129 |
130 | ) 131 | } -------------------------------------------------------------------------------- /src/Settings/Views/Password.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Grid from '@material-ui/core/Grid'; 5 | import Paper from '@material-ui/core/Paper'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import Edit from '@material-ui/icons/KeyboardArrowRight'; 8 | import TextField from '@material-ui/core/TextField'; 9 | import Divider from '@material-ui/core/Divider'; 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | root: { 13 | flexGrow: 1, 14 | }, 15 | paper: { 16 | padding: theme.spacing(2), 17 | textAlign: 'center', 18 | color: theme.palette.text.secondary, 19 | }, 20 | rightIcon: { 21 | fontSize: '1em', 22 | }, 23 | textField: { 24 | marginLeft: theme.spacing(1), 25 | marginRight: theme.spacing(1), 26 | }, 27 | label: { 28 | letterSpacing: '.07272727em', 29 | fontSize: '.6875rem', 30 | fontWeight: 500, 31 | lineHeight: '1rem', 32 | textTransform: 'uppercase', 33 | color: '#5f6368', 34 | marginLeft: '10px', 35 | }, 36 | row: { 37 | // margin: theme.spacing(1), 38 | paddingTop: theme.spacing(1), 39 | '&:hover': { 40 | backgroundColor: '#f5f5f5', 41 | cursor: 'pointer' 42 | } 43 | } 44 | })); 45 | 46 | function a() { 47 | return alert('hi'); 48 | } 49 | 50 | export default function Password() { 51 | const classes = useStyles(); 52 | 53 | const date = new Date().toLocaleString('en-US'); 54 | 55 | return ( 56 |
57 | 58 | Password 59 | 60 | 61 |
62 | 63 | 64 | 65 | Password 66 | 67 | 68 | 69 | 70 | ••••••••• 71 | 72 | 73 | 74 | 75 | Last changed {date} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 |
86 | 87 | 88 | 89 | 2-Step Verification 90 | 91 | 92 | 93 | 94 | Disabled 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
103 | 104 |
105 | ) 106 | } -------------------------------------------------------------------------------- /src/Settings/Views/UserGroups.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Grid from '@material-ui/core/Grid'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Edit from '@material-ui/icons/KeyboardArrowRight'; 6 | import Divider from '@material-ui/core/Divider'; 7 | import Table from '@material-ui/core/Table'; 8 | import TableBody from '@material-ui/core/TableBody'; 9 | import TableCell from '@material-ui/core/TableCell'; 10 | import TableHead from '@material-ui/core/TableHead'; 11 | import TableRow from '@material-ui/core/TableRow'; 12 | import Paper from '@material-ui/core/Paper'; 13 | import Button from '@material-ui/core/Button'; 14 | import AddIcon from '@material-ui/icons/Add'; 15 | import Container from '@material-ui/core/Container'; 16 | 17 | const useStyles = makeStyles(theme => ({ 18 | root: { 19 | flexGrow: 1, 20 | }, 21 | paper: { 22 | padding: theme.spacing(2), 23 | textAlign: 'center', 24 | color: theme.palette.text.secondary, 25 | }, 26 | rightIcon: { 27 | fontSize: '1em', 28 | }, 29 | textField: { 30 | marginLeft: theme.spacing(1), 31 | marginRight: theme.spacing(1), 32 | }, 33 | label: { 34 | letterSpacing: '.07272727em', 35 | fontSize: '.6875rem', 36 | fontWeight: 500, 37 | lineHeight: '1rem', 38 | textTransform: 'uppercase', 39 | color: '#5f6368', 40 | }, 41 | row: { 42 | // margin: theme.spacing(1), 43 | paddingTop: theme.spacing(1), 44 | '&:hover': { 45 | backgroundColor: '#f5f5f5', 46 | cursor: 'pointer' 47 | } 48 | }, 49 | paper: { 50 | width: '100%', 51 | marginTop: theme.spacing(2), 52 | overflowX: 'auto', 53 | boxShadow: '0 0 1px 0 rgba(0,0,0,.22)', 54 | }, 55 | rightIcon: { 56 | marginLeft: theme.spacing(1), 57 | }, 58 | iconSmall: { 59 | fontSize: 20, 60 | }, 61 | button: { 62 | boxShadow: 'none', 63 | fontFamily: 'ApercuMedium', 64 | marginTop: theme.spacing(4), 65 | }, 66 | })); 67 | 68 | function a() { 69 | return alert('hi'); 70 | } 71 | 72 | export default function Password() { 73 | const classes = useStyles(); 74 | 75 | const date = new Date().toLocaleString('en-US'); 76 | 77 | return ( 78 |
79 | 80 | User Groups 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Login 90 | 91 | 92 | 93 | 94 | Password 95 | 96 | 97 | 98 | 99 | Email 100 | 101 | 102 | 103 | 104 | Role 105 | 106 | 107 | 108 | 109 | Date Created 110 | 111 | 112 | 113 | 114 | 115 | 116 | bobby.darcy 117 | ••••••••• 118 | bobby.darcy@company.com 119 | Admin 120 | 01/01/2010 121 | 122 | 123 | boris.chan 124 | ••••••••• 125 | boris.chan@company.com 126 | User 127 | 01/01/2013 128 | 129 | 130 |
131 |
132 | 133 | 137 | 138 |
139 | ) 140 | } -------------------------------------------------------------------------------- /src/Team/Member.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles, withStyles } from "@material-ui/core/styles"; 3 | import clsx from "clsx"; 4 | import Card from "@material-ui/core/Card"; 5 | import CardHeader from "@material-ui/core/CardHeader"; 6 | import CardMedia from "@material-ui/core/CardMedia"; 7 | import CardContent from "@material-ui/core/CardContent"; 8 | import CardActions from "@material-ui/core/CardActions"; 9 | import Button from "@material-ui/core/Button"; 10 | import Avatar from "@material-ui/core/Avatar"; 11 | import IconButton from "@material-ui/core/IconButton"; 12 | import Typography from "@material-ui/core/Typography"; 13 | import CommentIcon from "@material-ui/icons/Comment"; 14 | import ThumbUpIcon from "@material-ui/icons/ThumbUp"; 15 | import ExpandMoreIcon from "@material-ui/icons/MoreHoriz"; 16 | import Divider from "@material-ui/core/Divider"; 17 | import Badge from "@material-ui/core/Badge"; 18 | import Box from "@material-ui/core/Box"; 19 | 20 | import getAvatar from "../Common/AnimalAvatars"; 21 | 22 | const useStyles = makeStyles((theme) => ({ 23 | outerCard: { 24 | boxShadow: "0 0 11px #eaf0f6", 25 | border: "1px solid #eaf0f6", 26 | "&:hover": {}, 27 | }, 28 | card: { 29 | borderColor: "#fff", 30 | borderStyle: "solid", 31 | borderBottomWidth: "3px", 32 | borderLeftWidth: "0px", 33 | borderTopWidth: "0px", 34 | borderRightWidth: "0px", 35 | "&:hover": { 36 | borderColor: theme.palette.primary.light, 37 | borderStyle: "solid", 38 | borderBottomWidth: "3px", 39 | }, 40 | }, 41 | media: { 42 | height: 0, 43 | paddingTop: "56.25%", // 16:9 44 | }, 45 | expand: { 46 | marginLeft: "auto", 47 | }, 48 | expandOpen: { 49 | transform: "rotate(180deg)", 50 | }, 51 | avatar: { 52 | width: theme.spacing(7), 53 | height: theme.spacing(7), 54 | }, 55 | rightIcon: { 56 | marginLeft: theme.spacing(1), 57 | }, 58 | button: { 59 | marginLeft: "auto", 60 | }, 61 | name: { 62 | fontFamily: "ApercuMedium", 63 | }, 64 | subtitle: { 65 | color: theme.palette.action.active, 66 | }, 67 | })); 68 | 69 | export default function Member(props) { 70 | const classes = useStyles(); 71 | 72 | const handleClick = () => { 73 | if (props.openModal) { 74 | props.openModal(props.member); 75 | } 76 | }; 77 | 78 | return ( 79 | 80 | 81 | 88 | } 89 | action={ 90 | 91 | 92 | 93 | } 94 | title={props.title} 95 | subheader={props.date} 96 | /> 97 | 98 | 99 | {props.member.firstName} {props.member.lastName} 100 | 101 | 102 | {props.member.role} 103 | 104 | 108 | 109 | {props.member.email} 110 | 111 | 112 | {props.member.phone} 113 | 114 | 115 | 116 | 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /src/Team/Profile.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Container, 4 | Box, 5 | makeStyles, 6 | Avatar, 7 | Typography, 8 | Divider, 9 | TextField, 10 | Grid, 11 | Button, 12 | } from "@material-ui/core"; 13 | 14 | const useStyles = makeStyles((theme) => ({ 15 | root: {}, 16 | paper: { 17 | backgroundColor: theme.palette.background.paper, 18 | boxShadow: "0 20px 60px -2px rgba(27,33,58,.4)", 19 | padding: theme.spacing(2, 4, 3), 20 | borderRadius: "8px", 21 | width: 375, 22 | }, 23 | large: { 24 | width: theme.spacing(12), 25 | height: theme.spacing(12), 26 | }, 27 | name: { 28 | marginTop: theme.spacing(2), 29 | fontFamily: "ApercuBold", 30 | }, 31 | role: { 32 | marginTop: theme.spacing(2), 33 | marginBottom: theme.spacing(2), 34 | }, 35 | description: { 36 | marginTop: theme.spacing(2), 37 | marginBottom: theme.spacing(2), 38 | }, 39 | resize: { 40 | fontSize: "0.875em", 41 | }, 42 | })); 43 | 44 | export default function Profiles(props) { 45 | const classes = useStyles(); 46 | 47 | const { 48 | id, 49 | firstName, 50 | lastName, 51 | avatar, 52 | role, 53 | email, 54 | phone, 55 | location, 56 | description, 57 | } = props.member; 58 | 59 | return ( 60 | 61 | 62 | 63 | 64 | 65 | {`${firstName} ${lastName}`} 66 | 67 | 68 | {`${role}`} 69 | 70 | 71 | 84 | 85 | 86 | 96 | 97 | 98 | 108 | 109 | 110 | {/* 111 | 114 | */} 115 | 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/Team/Team.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import faker from "faker"; 4 | 5 | import { 6 | Container, 7 | Typography, 8 | Box, 9 | makeStyles, 10 | Select, 11 | MenuItem, 12 | IconButton, 13 | Grid, 14 | Avatar, 15 | Modal, 16 | Backdrop, 17 | Fade, 18 | } from "@material-ui/core"; 19 | 20 | import Table from "@material-ui/core/Table"; 21 | import TableBody from "@material-ui/core/TableBody"; 22 | import TableCell from "@material-ui/core/TableCell"; 23 | import TableContainer from "@material-ui/core/TableContainer"; 24 | import TableHead from "@material-ui/core/TableHead"; 25 | import TableRow from "@material-ui/core/TableRow"; 26 | import Paper from "@material-ui/core/Paper"; 27 | import blue from "@material-ui/core/colors/blue"; 28 | import grey from "@material-ui/core/colors/grey"; 29 | 30 | import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; 31 | import MoreIcon from "@material-ui/icons/MoreVert"; 32 | 33 | import SearchIcon from "@material-ui/icons/Search"; 34 | import ViewModuleIcon from "@material-ui/icons/ViewModule"; 35 | import ViewHeadlineIcon from "@material-ui/icons/ViewHeadline"; 36 | import UnfoldMoreIcon from "@material-ui/icons/UnfoldMore"; 37 | 38 | import PageTitle from "../Common/PageTitle"; 39 | import Member from "./Member"; 40 | import Profile from "./Profile"; 41 | 42 | const useStyles = makeStyles((theme) => ({ 43 | menuText: { 44 | fontFamily: "ApercuMedium", 45 | fontSize: "0.875em", 46 | marginBottom: "1px", 47 | color: theme.palette.action.active, 48 | }, 49 | select: { 50 | marginLeft: theme.spacing(1.5), 51 | fontSize: "0.875em", 52 | width: "75px", 53 | }, 54 | selectGroup: { 55 | // height: "50px", 56 | }, 57 | button: { 58 | margin: theme.spacing(1), 59 | }, 60 | avatar: { 61 | marginLeft: theme.spacing(1), 62 | }, 63 | root: { 64 | boxShadow: "0 0 11px #eaf0f6", 65 | borderRadius: "4px", 66 | overflow: "hidden", 67 | border: "1px solid #eaf0f6", 68 | }, 69 | table: {}, 70 | tableHead: { 71 | fontFamily: "ApercuMedium", 72 | fontSize: "0.875rem", 73 | color: "#525f7f", 74 | paddingLeft: theme.spacing(2), 75 | paddingTop: theme.spacing(2), 76 | paddingBottom: theme.spacing(2), 77 | // backgroundColor: grey[50], 78 | }, 79 | tableHeadAvatar: { 80 | width: "50px", 81 | }, 82 | tableHeadCell: { 83 | padding: theme.spacing(0), 84 | }, 85 | active: { 86 | color: theme.palette.primary.main, 87 | }, 88 | tableRow: { 89 | borderColor: "#fff", 90 | borderStyle: "solid", 91 | borderLeftWidth: "3px", 92 | borderBottomWidth: "0px", 93 | borderTopWidth: "0px", 94 | borderRightWidth: "3px", 95 | "&:hover": { 96 | borderColor: theme.palette.primary.light, 97 | borderStyle: "solid", 98 | borderLeftWidth: "3px", 99 | backgroundColor: blue[50], 100 | borderRightColor: blue[50], 101 | }, 102 | }, 103 | tableHeadRow: { 104 | backgroundColor: "#fff", 105 | borderColor: "#fff", 106 | borderStyle: "solid", 107 | borderLeftWidth: "3px", 108 | }, 109 | modal: { 110 | display: "flex", 111 | alignItems: "center", 112 | justifyContent: "center", 113 | }, 114 | active: { 115 | color: theme.palette.primary.main, 116 | }, 117 | })); 118 | 119 | const populate = (n = 1) => { 120 | const members = []; 121 | 122 | for (let i = 0; i < n; i++) { 123 | members.push({ 124 | id: i, 125 | firstName: faker.name.firstName(), 126 | lastName: faker.name.lastName(), 127 | avatar: faker.image.avatar(), 128 | role: faker.name.jobTitle(), 129 | email: faker.internet.email(), 130 | phone: faker.phone.phoneNumber(), 131 | location: `${faker.address.city()}, ${faker.address.stateAbbr()}`, 132 | description: faker.hacker.phrase(), 133 | }); 134 | } 135 | 136 | return members; 137 | }; 138 | 139 | const members = populate(7); 140 | 141 | export default function Team() { 142 | const classes = useStyles(); 143 | 144 | const [data, setData] = React.useState(members); 145 | 146 | /* -1: unsorted/unused 147 | 0: is NOT ascending 148 | 1: is ascending 149 | */ 150 | const [sortData, setSortData] = React.useState({ 151 | name: -1, 152 | role: -1, 153 | email: -1, 154 | }); 155 | 156 | const sortByAttribute = (attribute) => { 157 | const dataset = [...data]; 158 | 159 | if (sortData[attribute] < 1) { 160 | dataset.sort(function (a, b) { 161 | const al = a[attribute].toLowerCase(); 162 | const bl = b[attribute].toLowerCase(); 163 | // No swap if b is greater (ascending). 164 | if (al < bl) { 165 | return -1; 166 | } 167 | if (al > bl) { 168 | return 1; 169 | } 170 | return 0; 171 | }); 172 | const sortDataObj = { 173 | name: -1, 174 | role: -1, 175 | email: -1, 176 | }; 177 | sortDataObj[attribute] = 1; 178 | setSortData(sortDataObj); 179 | } else { 180 | dataset.reverse(); 181 | const sortDataObj = { 182 | name: -1, 183 | role: -1, 184 | email: -1, 185 | }; 186 | sortDataObj[attribute] = 0; 187 | setSortData(sortDataObj); 188 | } 189 | setData(dataset); 190 | }; 191 | 192 | const sortByName = () => { 193 | const dataset = [...data]; 194 | // attribute is unsorted. 195 | if (sortData.name < 1) { 196 | // alphabetic sort 197 | dataset.sort(function (a, b) { 198 | const al = a.firstName.toLowerCase(); 199 | const bl = b.firstName.toLowerCase(); 200 | // No swap if b is greater (ascending). 201 | if (al < bl) { 202 | return -1; 203 | } 204 | if (al > bl) { 205 | return 1; 206 | } 207 | return 0; 208 | }); 209 | setSortData({ 210 | name: -1, 211 | role: -1, 212 | email: -1, 213 | name: 1, 214 | }); 215 | } else { 216 | dataset.reverse(); 217 | setSortData({ 218 | name: -1, 219 | role: -1, 220 | email: -1, 221 | name: 0, 222 | }); 223 | } 224 | setData(dataset); 225 | }; 226 | 227 | const [modal, setModal] = React.useState({ 228 | open: false, 229 | member: null, 230 | }); 231 | 232 | const showModal = (member) => { 233 | setModal({ 234 | open: true, 235 | member, 236 | }); 237 | }; 238 | 239 | const handleClose = () => { 240 | setModal({ 241 | ...modal, 242 | open: false, 243 | }); 244 | }; 245 | 246 | const [showGrid, setShowGrid] = React.useState(true); 247 | const [sortBy, setSortBy] = React.useState("all"); 248 | 249 | const handleChange = (event) => { 250 | setSortBy(event.target.value); 251 | 252 | switch (event.target.value) { 253 | case "name": 254 | sortByName(); 255 | return; 256 | case "role": 257 | sortByAttribute("role"); 258 | return; 259 | case "email": 260 | sortByAttribute("email"); 261 | return; 262 | default: 263 | return; 264 | } 265 | }; 266 | 267 | const handleModalClose = () => { 268 | setModal({ 269 | ...modal, 270 | open: false, 271 | }); 272 | }; 273 | 274 | function DataDisplay() { 275 | function GridView() { 276 | return ( 277 | 284 | {data.map((member) => ( 285 | 286 | 287 | 288 | ))} 289 | 290 | ); 291 | } 292 | 293 | function TableView() { 294 | return ( 295 | 296 | 297 | 298 | 299 | 300 | 301 | 306 | Name 307 | 311 | -1 && classes.active)} 314 | /> 315 | 316 | 317 | 318 | 319 | 324 | Role 325 | sortByAttribute("role")} 328 | > 329 | -1 && classes.active)} 332 | /> 333 | 334 | 335 | 336 | 337 | 342 | Email 343 | sortByAttribute("email")} 346 | > 347 | -1 && classes.active)} 350 | /> 351 | 352 | 353 | 354 | 355 | Phone 356 | 357 | 358 | 359 | 360 | 361 | {data.map((member) => ( 362 | 363 | 364 | 369 | 370 | 371 | {member.firstName} {member.lastName} 372 | 373 | {member.role} 374 | {member.email} 375 | {member.phone} 376 | 377 | showModal(member)} 381 | > 382 | 383 | 384 | 385 | 386 | ))} 387 | 388 |
389 |
390 | ); 391 | } 392 | 393 | return showGrid ? : ; 394 | } 395 | 396 | return ( 397 | 398 | 399 | 400 | 401 | setShowGrid(true)} 404 | > 405 | 406 | 407 | setShowGrid(false)} 410 | > 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | {showGrid && ( 419 | 425 | Sort by: 426 | 437 | 438 | )} 439 | 440 | 441 | 451 |
452 | 453 |
454 |
455 |
456 | ); 457 | } 458 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { applyMiddleware, createStore, compose } from 'redux'; 5 | import thunk from 'redux-thunk'; 6 | import rootReducer from './store/reducers/rootReducer'; 7 | import jwtDecode from 'jwt-decode'; 8 | import { setCurrentUser } from './store/actions/auth'; 9 | import setAuthorizationToken from './utils/setAuthorizationToken'; 10 | import getTokenTimeRemaining from './utils/getTokenTimeRemaining'; 11 | 12 | import './index.css'; 13 | import App from './App'; 14 | 15 | // Redux dev-tools setup 16 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 17 | 18 | const store = createStore( 19 | rootReducer, composeEnhancers(applyMiddleware(thunk)) 20 | ) 21 | 22 | // Authorizes if user token is valid on page. 23 | if (localStorage.token) { 24 | 25 | const decodedToken = jwtDecode(localStorage.token); 26 | if (decodedToken.exp < new Date().getTime() / 1000) { 27 | console.log("EXPIRED"); 28 | } else { 29 | setAuthorizationToken(localStorage.token); 30 | store.dispatch(setCurrentUser(decodedToken)); 31 | getTokenTimeRemaining(decodedToken); 32 | } 33 | } 34 | 35 | ReactDOM.render( 36 | 37 | 38 | , 39 | document.getElementById('root') 40 | ); 41 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/store/actions/auth.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import setAuthorizationToken from "../../utils/setAuthorizationToken"; 3 | import jwtDecode from "jwt-decode"; 4 | import { 5 | SET_CURRENT_USER, 6 | SET_INVALID_CREDENTIALS, 7 | SET_INTERNAL_SERVER_ERROR, 8 | SET_CONNECTION_REFUSED_ERROR, 9 | } from "./types/types"; 10 | 11 | const URL = "http://localhost:8080"; 12 | 13 | export function setCurrentUser(user) { 14 | return { 15 | type: SET_CURRENT_USER, 16 | user, 17 | }; 18 | } 19 | 20 | export function setInvalidCredentials(errorStatus) { 21 | return { 22 | type: SET_INVALID_CREDENTIALS, 23 | error: { 24 | status: errorStatus, 25 | message: 26 | "There was an error with your username or password combination. Please try again.", 27 | }, 28 | }; 29 | } 30 | 31 | export function setInternalServerError(errorStatus) { 32 | return { 33 | type: SET_INTERNAL_SERVER_ERROR, 34 | error: { 35 | status: errorStatus, 36 | message: "An unexpected error occurred. Please try again later.", 37 | }, 38 | }; 39 | } 40 | 41 | export function setConnectionRefusedError() { 42 | return { 43 | type: SET_CONNECTION_REFUSED_ERROR, 44 | error: { 45 | status: 502, 46 | message: "Bad Gateway - Connection refused. Please try again later.", 47 | }, 48 | }; 49 | } 50 | 51 | export function userSignInRequest(userData) { 52 | return async (dispatch) => { 53 | try { 54 | const res = await axios.post(`${URL}/login`, userData); 55 | if (res.headers.authorization) { 56 | // Expect "Bearer " 57 | const token = res.headers.authorization.substring( 58 | 7, 59 | res.headers.authorization.length 60 | ); 61 | localStorage.setItem("token", token); 62 | setAuthorizationToken(token); 63 | // dispatch(setCurrentUser(jwtDecode(token))); 64 | 65 | const avatarId = await axios.get( 66 | `${URL}/users/avatar?username=${userData.username}` 67 | ); 68 | dispatch( 69 | setCurrentUser({ ...jwtDecode(token), avatarId: avatarId.data }) 70 | ); 71 | } 72 | } catch (error) { 73 | if (error.response) { 74 | if (error.response.status === 403) { 75 | dispatch(setInvalidCredentials(error.response.status)); 76 | } else { 77 | dispatch(setInternalServerError(error.response.status)); 78 | } 79 | } else { 80 | dispatch(setConnectionRefusedError()); 81 | } 82 | } 83 | }; 84 | } 85 | 86 | export function userSignOutRequest() { 87 | return (dispatch) => { 88 | localStorage.removeItem("token"); 89 | // Remove authorization header from future requests. 90 | setAuthorizationToken(false); 91 | dispatch(setCurrentUser({})); 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/store/actions/products.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { FETCH_PRODUCTS_SUCCESS, FETCH_PRODUCTS_ERROR, FETCH_PRODUCTS_PENDING } from './types/types'; 3 | 4 | const URL = 'http://localhost:8080'; 5 | 6 | export function fetchProductsPending() { 7 | return { 8 | type: FETCH_PRODUCTS_PENDING 9 | } 10 | } 11 | 12 | export function fetchProductsSuccess(products) { 13 | return { 14 | type: FETCH_PRODUCTS_SUCCESS, 15 | products 16 | } 17 | } 18 | 19 | export function fetchProductsError(error) { 20 | return { 21 | type: FETCH_PRODUCTS_ERROR, 22 | error 23 | } 24 | } 25 | 26 | export function fetchProducts() { 27 | return async (dispatch) => { 28 | try { 29 | dispatch(fetchProductsPending()); 30 | const res = await axios.get(`${URL}/products`); 31 | if (res.data && res.status === 200) { 32 | console.log(res); 33 | dispatch(fetchProductsSuccess(res.data)) 34 | } 35 | } catch (error) { 36 | dispatch(fetchProductsError(error)); 37 | console.log(error); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/store/actions/types/types.js: -------------------------------------------------------------------------------- 1 | export const SET_CURRENT_USER = 'SET_CURRENT_USER'; 2 | export const SET_INVALID_CREDENTIALS = 'SET_INVALID_CREDENTIALS'; 3 | export const SET_INTERNAL_SERVER_ERROR = 'SET_INTERNAL_SERVER_ERROR'; 4 | export const SET_CONNECTION_REFUSED_ERROR = 'SET_CONNECTION_REFUSED_ERROR'; 5 | 6 | export const FETCH_PRODUCTS_PENDING = 'FETCH_PRODUCTS_PENDING'; 7 | export const FETCH_PRODUCTS_SUCCESS = 'FETCH_PRODUCTS_SUCCESS'; 8 | export const FETCH_PRODUCTS_ERROR = 'FETCH_PRODUCTS_ERROR'; 9 | -------------------------------------------------------------------------------- /src/store/reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { SET_CURRENT_USER, SET_INVALID_CREDENTIALS, SET_INTERNAL_SERVER_ERROR, SET_CONNECTION_REFUSED_ERROR } from '../actions/types/types'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | 4 | const initialState = { 5 | isAuthenticated: false, 6 | user: {}, 7 | error: {} 8 | } 9 | 10 | export default (state = initialState, action = {}) => { 11 | switch (action.type) { 12 | case SET_CURRENT_USER: 13 | return { 14 | isAuthenticated: !isEmpty(action.user), 15 | user: action.user, 16 | error: {} 17 | } 18 | 19 | case SET_INVALID_CREDENTIALS: 20 | return { 21 | isAuthenticated: false, 22 | user: {}, 23 | error: { 24 | message: action.error, 25 | variant: 'error' 26 | } 27 | } 28 | 29 | case SET_INTERNAL_SERVER_ERROR: 30 | return { 31 | isAuthenticated: false, 32 | user: {}, 33 | error: { 34 | message: action.error, 35 | variant: 'warning' 36 | } 37 | } 38 | 39 | case SET_CONNECTION_REFUSED_ERROR: 40 | return { 41 | isAuthenticated: false, 42 | user: {}, 43 | error: { 44 | message: action.error, 45 | variant: 'error' 46 | } 47 | } 48 | 49 | default: 50 | return state; 51 | } 52 | } -------------------------------------------------------------------------------- /src/store/reducers/message.js: -------------------------------------------------------------------------------- 1 | /* Reducers are simple functions which take State and Actions, 2 | and returns new State. 3 | */ 4 | export default (state = [], action = {}) => { 5 | return state; 6 | } -------------------------------------------------------------------------------- /src/store/reducers/product.js: -------------------------------------------------------------------------------- 1 | import { FETCH_PRODUCTS_SUCCESS, FETCH_PRODUCTS_ERROR, FETCH_PRODUCTS_PENDING } from '../actions/types/types'; 2 | 3 | const initialState = { 4 | pending: false, 5 | error: null, 6 | products: [] 7 | } 8 | 9 | export default (state = initialState, action = {}) => { 10 | switch (action.type) { 11 | case FETCH_PRODUCTS_PENDING: 12 | return { 13 | pending: true, 14 | error: null, 15 | products: [] 16 | } 17 | 18 | case FETCH_PRODUCTS_ERROR: 19 | return { 20 | pending: false, 21 | error: action.error, 22 | products: [] 23 | } 24 | 25 | case FETCH_PRODUCTS_SUCCESS: 26 | return { 27 | pending: false, 28 | error: null, 29 | products: action.products 30 | } 31 | 32 | default: 33 | return state; 34 | } 35 | } -------------------------------------------------------------------------------- /src/store/reducers/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import product from './product'; 3 | import auth from './auth'; 4 | 5 | export default combineReducers({ 6 | product, 7 | auth 8 | }); -------------------------------------------------------------------------------- /src/utils/getTokenTimeRemaining.js: -------------------------------------------------------------------------------- 1 | export default function getTokenTimeRemaining(decodedToken) { 2 | const now = new Date().getTime() / 1000; 3 | const diff = decodedToken.exp - now; 4 | console.log(`Hours left: ${new Date(diff * 1000).getHours()}, Minutes: ${new Date(diff * 1000).getMinutes()}`); 5 | } -------------------------------------------------------------------------------- /src/utils/setAuthorizationToken.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // Given a token, we want to add token to headers for every request. 4 | export default function setAuthorizationToken(token) { 5 | if (token) { 6 | axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; 7 | } else { 8 | delete axios.defaults.headers.common['Authorization']; 9 | } 10 | } --------------------------------------------------------------------------------