From 9c8485da6ddbda1665f4375f6519dfed952499e4 Mon Sep 17 00:00:00 2001 From: Karel Hala <khala@redhat.com> Date: Mon, 23 Mar 2020 00:09:00 +0100 Subject: [PATCH] Add dashboard layout --- package.json | 2 + src/App.js | 123 ++++++++++++++++++++++++--- src/bordel/form-input.js | 20 ----- src/bordel/form.js | 36 -------- src/components/NavBar.js | 155 +++++++++++++++++++++++++++++------ src/components/Navigation.js | 36 ++++++++ src/views/Home.js | 1 - src/views/bordel.js | 11 --- yarn.lock | 9 +- 9 files changed, 286 insertions(+), 107 deletions(-) delete mode 100644 src/bordel/form-input.js delete mode 100644 src/bordel/form.js create mode 100644 src/components/Navigation.js delete mode 100644 src/views/bordel.js diff --git a/package.json b/package.json index 8eac39e..cae8988 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "dependencies": { "@auth0/auth0-spa-js": "^1.0.2", "@material-ui/core": "^4.9.5", + "@material-ui/icons": "^4.9.1", "axios": "^0.19.0", + "clsx": "^1.1.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-router-dom": "^5.0.0", diff --git a/src/App.js b/src/App.js index a218e0f..09498bc 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ -import React from "react"; -import { Router, Route, Switch, Link } from "react-router-dom"; +import React, { useState } from "react"; +import { Router, Route, Switch } from "react-router-dom"; import PrivateRoute from "./components/PrivateRoute"; import Loading from "./components/Loading"; @@ -8,26 +8,123 @@ import Home from "./views/Home"; import Profile from "./views/Profile"; import { useAuth0 } from "./react-auth0-spa"; import history from "./utils/history"; +import clsx from "clsx"; +import { makeStyles } from "@material-ui/core/styles"; +import CssBaseline from "@material-ui/core/CssBaseline"; +import Drawer from "@material-ui/core/Drawer"; +import Divider from "@material-ui/core/Divider"; +import IconButton from "@material-ui/core/IconButton"; +import Container from "@material-ui/core/Container"; +import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; +import Navigation from "./components/Navigation"; -import Bordel from './views/bordel'; +const drawerWidth = 240; + +const useStyles = makeStyles((theme) => ({ + root: { + display: "flex" + }, + toolbarIcon: { + display: "flex", + alignItems: "center", + justifyContent: "flex-end", + padding: "0 8px", + ...theme.mixins.toolbar + }, + title: { + flexGrow: 1 + }, + drawerPaper: { + position: "relative", + whiteSpace: "nowrap", + width: drawerWidth, + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen + }) + }, + drawerPaperClose: { + overflowX: "hidden", + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }), + width: theme.spacing(7), + [theme.breakpoints.up("sm")]: { + width: theme.spacing(9) + } + }, + appBarSpacer: theme.mixins.toolbar, + content: { + flexGrow: 1, + height: "100vh", + overflow: "auto" + }, + container: { + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4) + }, + paper: { + padding: theme.spacing(2), + display: "flex", + overflow: "auto", + flexDirection: "column" + }, + fixedHeight: { + height: 240 + } +})); const App = () => { - const { loading } = useAuth0(); + const { loading, isAuthenticated } = useAuth0(); + const classes = useStyles(); + const [open, setOpen] = useState(true); if (loading) { return <Loading />; } + const handleDrawerOpen = () => { + setOpen(true); + }; + const handleDrawerClose = () => { + setOpen(false); + }; + return ( - <Router history={history}> - <NavBar /> - <Link to="/testing">Take me to tests</Link> - <Switch> - <Route path="/" exact component={Home} /> - <PrivateRoute path="/profile" component={Profile} /> - <Route path="/testing" component={Bordel} /> - </Switch> - </Router> + <div className={classes.root}> + <Router history={history}> + <CssBaseline /> + <NavBar open={open} handleOpen={handleDrawerOpen} /> + {isAuthenticated && ( + <Drawer + variant="permanent" + classes={{ + paper: clsx(classes.drawerPaper, !open && classes.drawerPaperClose) + }} + open={open} + > + <div className={classes.toolbarIcon}> + <IconButton onClick={handleDrawerClose}> + <ChevronLeftIcon /> + </IconButton> + </div> + <Divider /> + <Navigation /> + <Divider /> + </Drawer> + )} + <main className={classes.content}> + <div className={classes.appBarSpacer} /> + <Container maxWidth="lg" className={classes.container}> + <Switch> + <Route path="/" exact component={Home} /> + <PrivateRoute path="/profile" component={Profile} /> + </Switch> + </Container> + </main> + </Router> + </div> ); }; diff --git a/src/bordel/form-input.js b/src/bordel/form-input.js deleted file mode 100644 index 1666cd1..0000000 --- a/src/bordel/form-input.js +++ /dev/null @@ -1,20 +0,0 @@ -import React, { useContext } from "react"; -import PropTypes from "prop-types"; -import { FormContext } from "./form"; - -const FormInput = ({ name, label, ...rest }) => { - const { onChange, values } = useContext(FormContext); - return ( - <div> - <label htmlFor={name}>{label}</label> - <input id={name} name={name} {...rest} value={values[name] || ''} onChange={({ target: { value } }) => onChange(value, name)} /> - </div> - ); -}; - -FormInput.propTypes = { - name: PropTypes.string.isRequired, - label: PropTypes.string -}; - -export default FormInput; diff --git a/src/bordel/form.js b/src/bordel/form.js deleted file mode 100644 index d42997e..0000000 --- a/src/bordel/form.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useState, createContext } from "react"; -import FormInput from "./form-input"; - -export const FormContext = createContext({}); - -const Form = () => { - const [values, setValues] = useState({ - "first-name": "Martin", - "last-name": "Hura" - }); - - const onChange = (value, name) => - setValues((prevValues) => ({ - ...prevValues, - [name]: value - })); - return ( - <FormContext.Provider - value={{ - values, - onChange - }} - > - <form> - <FormInput name="first-name" label="First name" /> - <FormInput name="last-name" label="Last name" /> - <FormInput name="age" label="Age" type="number" /> - <button type="button" onClick={() => console.log(values)}> - Submit - </button> - </form> - </FormContext.Provider> - ); -}; - -export default Form; diff --git a/src/components/NavBar.js b/src/components/NavBar.js index 29151f3..46951d4 100644 --- a/src/components/NavBar.js +++ b/src/components/NavBar.js @@ -1,10 +1,75 @@ -import React from "react"; -import { NavLink as RouterNavLink } from "react-router-dom"; - +import React, { useState } from "react"; +import PropTypes from "prop-types"; import { useAuth0 } from "../react-auth0-spa"; +import clsx from "clsx"; +import { makeStyles } from "@material-ui/core/styles"; +import AppBar from "@material-ui/core/AppBar"; +import Toolbar from "@material-ui/core/Toolbar"; +import Typography from "@material-ui/core/Typography"; +import IconButton from "@material-ui/core/IconButton"; +import Badge from "@material-ui/core/Badge"; +import NotificationsIcon from "@material-ui/icons/Notifications"; +import MenuIcon from "@material-ui/icons/Menu"; +import Button from "@material-ui/core/Button"; +import Avatar from "@material-ui/core/Avatar"; +import Menu from "@material-ui/core/Menu"; +import MenuItem from "@material-ui/core/MenuItem"; +import { useHistory } from "react-router-dom"; + +const drawerWidth = 240; -const NavBar = () => { +const useStyles = makeStyles((theme) => { + console.log(theme); + return { + appBar: { + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }) + }, + appBarShift: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen + }) + }, + toolbar: { + paddingRight: 24 // keep right padding when drawer closed + }, + menuButton: { + marginRight: 36 + }, + menuButtonHidden: { + display: "none" + }, + title: { + flexGrow: 1 + }, + avatar: { + color: theme.palette.common.white + }, + avatarCircle: { + marginRight: theme.spacing(2) + } + }; +}); + +const NavBar = ({ open, handleOpen }) => { const { user, isAuthenticated, loginWithRedirect, logout } = useAuth0(); + const classes = useStyles(); + const [anchorEl, setAnchorEl] = useState(null); + const history = useHistory(); + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; const logoutWithRedirect = () => logout({ @@ -12,28 +77,68 @@ const NavBar = () => { }); return ( - <div className="nav-container"> - {!isAuthenticated && ( - <button id="qsLoginBtn" type="button" onClick={() => loginWithRedirect({})}> - Log in - </button> - )} - {isAuthenticated && ( - <ul> - <li> - <img src={user.picture} alt="Profile" className="nav-user-profile rounded-circle" width="50" /> - {user.name} - </li> - <li> - <RouterNavLink to="/profile">Profile</RouterNavLink> - </li> - <li> - <button onClick={() => logoutWithRedirect()}>Log out</button> - </li> - </ul> - )} - </div> + <AppBar position="absolute" className={clsx(classes.appBar, isAuthenticated && open && classes.appBarShift)}> + <Toolbar className={classes.toolbar}> + {isAuthenticated && ( + <IconButton + edge="start" + color="inherit" + aria-label="open drawer" + onClick={handleOpen} + className={clsx(classes.menuButton, open && classes.menuButtonHidden)} + > + <MenuIcon /> + </IconButton> + )} + <Typography component="h1" variant="h6" color="inherit" noWrap className={classes.title}> + Dashboard + </Typography> + {isAuthenticated && ( + <IconButton color="inherit"> + <Badge badgeContent={4} color="secondary"> + <NotificationsIcon /> + </Badge> + </IconButton> + )} + {!isAuthenticated && ( + <Button id="qsLoginBtn" variant="contained" type="button" onClick={() => loginWithRedirect({})}> + Log in + </Button> + )} + {isAuthenticated && ( + <div> + <Button className={clsx(classes.avatar)} aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}> + <Avatar src={user.picture} alt="Profile" className={clsx(classes.avatarCircle)} /> + {user.name} + </Button> + <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}> + <MenuItem + onClick={() => { + handleClose(); + history.push("/profile"); + }} + > + Profile + </MenuItem> + <MenuItem + onClick={() => { + handleClose(); + logoutWithRedirect(); + }} + > + Log out + </MenuItem> + </Menu> + </div> + )} + </Toolbar> + </AppBar> ); }; +NavBar.propTypes = { + open: PropTypes.bool, + handleOpen: PropTypes.func +}; + export default NavBar; diff --git a/src/components/Navigation.js b/src/components/Navigation.js new file mode 100644 index 0000000..1ec233f --- /dev/null +++ b/src/components/Navigation.js @@ -0,0 +1,36 @@ +import React from "react"; +import clsx from "clsx"; +import ListItem from "@material-ui/core/ListItem"; +import ListItemIcon from "@material-ui/core/ListItemIcon"; +import ListItemText from "@material-ui/core/ListItemText"; +import DashboardIcon from "@material-ui/icons/Dashboard"; +import { NavLink as RouterNavLink } from "react-router-dom"; +import { makeStyles } from "@material-ui/core/styles"; + +const useStyles = makeStyles((theme) => ({ + link: { + textDecoration: "none", + color: theme.palette.text.primary, + "&:hover": { + textDecoration: "none" + } + } +})); + +const Navigation = () => { + const styles = useStyles(); + return ( + <div> + <RouterNavLink to="/" className={clsx(styles.link)} activeClassName="selected"> + <ListItem button> + <ListItemIcon> + <DashboardIcon /> + </ListItemIcon> + <ListItemText primary="Dashboard" /> + </ListItem> + </RouterNavLink> + </div> + ); +}; + +export default Navigation; diff --git a/src/views/Home.js b/src/views/Home.js index 517df01..f89852c 100644 --- a/src/views/Home.js +++ b/src/views/Home.js @@ -35,7 +35,6 @@ const headerTemplate = { const DataTable = ({ rows }) => { const classes = useStyles(); - console.log(classes); return ( <TableContainer className={classes.tableContainer} component={Paper}> <Table className={classes.table} aria-label="simple table"> diff --git a/src/views/bordel.js b/src/views/bordel.js deleted file mode 100644 index 1426a96..0000000 --- a/src/views/bordel.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import Form from '../bordel/form'; - -const Bordel = () => ( - <div> - <h1>There is a form</h1> - <Form /> - </div> -) - -export default Bordel; diff --git a/yarn.lock b/yarn.lock index 40dcee5..bd23de3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1213,6 +1213,13 @@ react-is "^16.8.0" react-transition-group "^4.3.0" +"@material-ui/icons@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.9.1.tgz#fdeadf8cb3d89208945b33dbc50c7c616d0bd665" + integrity sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles@^4.9.0": version "4.9.0" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.9.0.tgz#10c31859f6868cfa9d3adf6b6c3e32c9d676bc76" @@ -2668,7 +2675,7 @@ clone-deep@^2.0.1: kind-of "^6.0.0" shallow-clone "^1.0.0" -clsx@^1.0.2: +clsx@^1.0.2, clsx@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA== -- GitLab