Merge pull request #600 from GlancingMind/upstream-improve-navigation

Michael Muré created

WebUI: Improve navigation

Change summary

webui/public/logo-alpha-flat-outline.svg           |  2 
webui/src/App.tsx                                  |  2 
webui/src/components/BackToListButton.tsx          | 36 +++++++
webui/src/components/BugTitleForm/BugTitleForm.tsx |  2 
webui/src/components/Content/PreTag.tsx            |  2 
webui/src/components/Header/Header.tsx             | 73 ++++++++++++++-
webui/src/pages/bug/Bug.tsx                        | 19 ++-
webui/src/pages/bug/BugQuery.tsx                   |  4 
webui/src/pages/bug/CommentForm.tsx                |  1 
webui/src/pages/list/FilterToolbar.tsx             |  2 
webui/src/pages/new/NewBugPage.tsx                 | 11 +
webui/src/pages/notfound/NotFoundPage.tsx          | 52 +++++++++++
webui/src/themes/DefaultDark.ts                    |  3 
webui/src/themes/DefaultLight.ts                   |  4 
14 files changed, 191 insertions(+), 22 deletions(-)

Detailed changes

webui/src/App.tsx 🔗

@@ -5,6 +5,7 @@ import Layout from './components/Header';
 import BugPage from './pages/bug';
 import ListPage from './pages/list';
 import NewBugPage from './pages/new/NewBugPage';
+import NotFoundPage from './pages/notfound/NotFoundPage';
 
 export default function App() {
   return (
@@ -13,6 +14,7 @@ export default function App() {
         <Route path="/" exact component={ListPage} />
         <Route path="/new" exact component={NewBugPage} />
         <Route path="/bug/:id" exact component={BugPage} />
+        <Route component={NotFoundPage} />
       </Switch>
     </Layout>
   );

webui/src/components/BackToListButton.tsx 🔗

@@ -0,0 +1,36 @@
+import React from 'react';
+
+import Button from '@material-ui/core/Button';
+import { makeStyles } from '@material-ui/core/styles';
+import ArrowBackIcon from '@material-ui/icons/ArrowBack';
+
+const useStyles = makeStyles((theme) => ({
+  backButton: {
+    position: 'sticky',
+    top: '80px',
+    backgroundColor: theme.palette.primary.dark,
+    color: theme.palette.primary.contrastText,
+    '&:hover': {
+      backgroundColor: theme.palette.primary.main,
+      color: theme.palette.primary.contrastText,
+    },
+  },
+}));
+
+function BackToListButton() {
+  const classes = useStyles();
+
+  return (
+    <Button
+      variant="contained"
+      className={classes.backButton}
+      aria-label="back to issue list"
+      href="/"
+    >
+      <ArrowBackIcon />
+      Back to List
+    </Button>
+  );
+}
+
+export default BackToListButton;

webui/src/components/BugTitleForm/BugTitleForm.tsx 🔗

@@ -70,7 +70,7 @@ function BugTitleForm({ bug }: Props) {
 
   function isFormValid() {
     if (issueTitleInput) {
-      return issueTitleInput.value.length > 0 ? true : false;
+      return issueTitleInput.value.length > 0;
     } else {
       return false;
     }

webui/src/components/Content/PreTag.tsx 🔗

@@ -11,7 +11,7 @@ const useStyles = makeStyles({
 
 const PreTag = (props: React.HTMLProps<HTMLPreElement>) => {
   const classes = useStyles();
-  return <pre className={classes.tag} {...props}></pre>;
+  return <pre className={classes.tag} {...props} />;
 };
 
 export default PreTag;

webui/src/components/Header/Header.tsx 🔗

@@ -1,12 +1,15 @@
 import React from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useLocation } from 'react-router-dom';
 
 import AppBar from '@material-ui/core/AppBar';
+import Tab, { TabProps } from '@material-ui/core/Tab';
+import Tabs from '@material-ui/core/Tabs';
 import Toolbar from '@material-ui/core/Toolbar';
+import Tooltip from '@material-ui/core/Tooltip/Tooltip';
 import { makeStyles } from '@material-ui/core/styles';
 
-import { LightSwitch } from '../../components/Themer';
 import CurrentIdentity from '../CurrentIdentity/CurrentIdentity';
+import { LightSwitch } from '../Themer';
 
 const useStyles = makeStyles((theme) => ({
   offset: {
@@ -15,9 +18,13 @@ const useStyles = makeStyles((theme) => ({
   filler: {
     flexGrow: 1,
   },
+  appBar: {
+    backgroundColor: theme.palette.primary.dark,
+    color: theme.palette.primary.contrastText,
+  },
   appTitle: {
     ...theme.typography.h6,
-    color: 'white',
+    color: theme.palette.primary.contrastText,
     textDecoration: 'none',
     display: 'flex',
     alignItems: 'center',
@@ -31,18 +38,53 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
+function a11yProps(index: any) {
+  return {
+    id: `nav-tab-${index}`,
+    'aria-controls': `nav-tabpanel-${index}`,
+  };
+}
+
+const DisabledTabWithTooltip = (props: TabProps) => {
+  /*The span elements around disabled tabs are needed, as the tooltip
+   * won't be triggered by disabled elements.
+   * See: https://material-ui.com/components/tooltips/#disabled-elements
+   * This must be done in a wrapper component, otherwise the TabS component
+   * cannot pass it styles down to the Tab component. Resulting in (console)
+   * warnings. This wrapper acceps the passed down TabProps and pass it around
+   * the span element to the Tab component.
+   */
+  const msg = `This feature doesn't exist yet. Come help us build it.`;
+  return (
+    <Tooltip title={msg}>
+      <span>
+        <Tab disabled {...props} />
+      </span>
+    </Tooltip>
+  );
+};
+
 function Header() {
   const classes = useStyles();
+  const location = useLocation();
+  const [selectedTab, setTab] = React.useState(location.pathname);
+
+  const handleTabClick = (
+    event: React.ChangeEvent<{}>,
+    newTabValue: string
+  ) => {
+    setTab(newTabValue);
+  };
 
   return (
     <>
-      <AppBar position="fixed" color="primary">
+      <AppBar position="fixed" className={classes.appBar}>
         <Toolbar>
           <Link to="/" className={classes.appTitle}>
-            <img src="/logo.svg" className={classes.logo} alt="git-bug" />
+            <img src="/logo.svg" className={classes.logo} alt="git-bug logo" />
             git-bug
           </Link>
-          <div className={classes.filler}></div>
+          <div className={classes.filler} />
           <div className={classes.lightSwitch}>
             <LightSwitch />
           </div>
@@ -50,6 +92,25 @@ function Header() {
         </Toolbar>
       </AppBar>
       <div className={classes.offset} />
+      <Tabs
+        centered
+        value={selectedTab}
+        onChange={handleTabClick}
+        aria-label="nav tabs"
+      >
+        <DisabledTabWithTooltip label="Code" value="/code" {...a11yProps(1)} />
+        <Tab label="Bugs" value="/" component={Link} to="/" {...a11yProps(2)} />
+        <DisabledTabWithTooltip
+          label="Pull Requests"
+          value="/pulls"
+          {...a11yProps(3)}
+        />
+        <DisabledTabWithTooltip
+          label="Settings"
+          value="/settings"
+          {...a11yProps(4)}
+        />
+      </Tabs>
     </>
   );
 }

webui/src/pages/bug/Bug.tsx 🔗

@@ -18,11 +18,17 @@ const useStyles = makeStyles((theme) => ({
     maxWidth: 1000,
     margin: 'auto',
     marginTop: theme.spacing(4),
-    overflow: 'hidden',
   },
   header: {
-    marginLeft: theme.spacing(3) + 40,
     marginRight: theme.spacing(2),
+    marginLeft: theme.spacing(3) + 40,
+  },
+  title: {
+    ...theme.typography.h5,
+  },
+  id: {
+    ...theme.typography.subtitle1,
+    marginLeft: theme.spacing(1),
   },
   container: {
     display: 'flex',
@@ -36,11 +42,11 @@ const useStyles = makeStyles((theme) => ({
     marginRight: theme.spacing(2),
     minWidth: 400,
   },
-  sidebar: {
+  rightSidebar: {
     marginTop: theme.spacing(2),
     flex: '0 0 200px',
   },
-  sidebarTitle: {
+  rightSidebarTitle: {
     fontWeight: 'bold',
   },
   labelList: {
@@ -75,7 +81,6 @@ function Bug({ bug }: Props) {
       <div className={classes.header}>
         <BugTitleForm bug={bug} />
       </div>
-
       <div className={classes.container}>
         <div className={classes.timeline}>
           <TimelineQuery id={bug.id} />
@@ -87,8 +92,8 @@ function Bug({ bug }: Props) {
             )}
           </IfLoggedIn>
         </div>
-        <div className={classes.sidebar}>
-          <span className={classes.sidebarTitle}>Labels</span>
+        <div className={classes.rightSidebar}>
+          <span className={classes.rightSidebarTitle}>Labels</span>
           <ul className={classes.labelList}>
             {bug.labels.length === 0 && (
               <span className={classes.noLabel}>None yet</span>

webui/src/pages/bug/BugQuery.tsx 🔗

@@ -3,6 +3,8 @@ import { RouteComponentProps } from 'react-router-dom';
 
 import CircularProgress from '@material-ui/core/CircularProgress';
 
+import NotFoundPage from '../notfound/NotFoundPage';
+
 import Bug from './Bug';
 import { useGetBugQuery } from './BugQuery.generated';
 
@@ -15,8 +17,8 @@ const BugQuery: React.FC<Props> = ({ match }: Props) => {
     variables: { id: match.params.id },
   });
   if (loading) return <CircularProgress />;
+  if (!data?.repository?.bug) return <NotFoundPage />;
   if (error) return <p>Error: {error}</p>;
-  if (!data?.repository?.bug) return <p>404.</p>;
   return <Bug bug={data.repository.bug} />;
 };
 

webui/src/pages/bug/CommentForm.tsx 🔗

@@ -28,6 +28,7 @@ const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
   },
   actions: {
     display: 'flex',
+    gap: '1em',
     justifyContent: 'flex-end',
   },
   greenButton: {

webui/src/pages/list/FilterToolbar.tsx 🔗

@@ -40,7 +40,7 @@ function CountingFilter({ query, children, ...props }: CountingFilterProps) {
     variables: { query },
   });
 
-  var prefix;
+  let prefix;
   if (loading) prefix = '...';
   else if (error || !data?.repository) prefix = '???';
   // TODO: better prefixes & error handling

webui/src/pages/new/NewBugPage.tsx 🔗

@@ -1,7 +1,7 @@
 import React, { FormEvent, useState } from 'react';
+import { useHistory } from 'react-router-dom';
 
-import { Button } from '@material-ui/core';
-import Paper from '@material-ui/core/Paper';
+import { Button, Paper } from '@material-ui/core';
 import { makeStyles, Theme } from '@material-ui/core/styles';
 
 import BugTitleInput from '../../components/BugTitleForm/BugTitleInput';
@@ -47,7 +47,9 @@ function NewBugPage() {
   const [issueTitle, setIssueTitle] = useState('');
   const [issueComment, setIssueComment] = useState('');
   const classes = useStyles();
+
   let issueTitleInput: any;
+  let history = useHistory();
 
   function submitNewIssue(e: FormEvent) {
     e.preventDefault();
@@ -59,12 +61,15 @@ function NewBugPage() {
           message: issueComment,
         },
       },
+    }).then(function (data) {
+      const id = data.data?.newBug.bug.humanId;
+      history.push('/bug/' + id);
     });
     issueTitleInput.value = '';
   }
 
   function isFormValid() {
-    return issueTitle.length > 0 && issueComment.length > 0 ? true : false;
+    return issueTitle.length > 0;
   }
 
   if (loading) return <div>Loading...</div>;

webui/src/pages/notfound/NotFoundPage.tsx 🔗

@@ -0,0 +1,52 @@
+import React from 'react';
+
+import { makeStyles } from '@material-ui/core/styles';
+
+import BackToListButton from '../../components/BackToListButton';
+
+const useStyles = makeStyles((theme) => ({
+  main: {
+    maxWidth: 1000,
+    margin: 'auto',
+    marginTop: theme.spacing(10),
+  },
+  logo: {
+    height: '350px',
+    display: 'block',
+    marginLeft: 'auto',
+    marginRight: 'auto',
+  },
+  icon: {
+    display: 'block',
+    marginLeft: 'auto',
+    marginRight: 'auto',
+    fontSize: '80px',
+  },
+  backLink: {
+    marginTop: theme.spacing(1),
+    textAlign: 'center',
+  },
+  header: {
+    fontSize: '30px',
+    textAlign: 'center',
+  },
+}));
+
+function NotFoundPage() {
+  const classes = useStyles();
+  return (
+    <main className={classes.main}>
+      <h1 className={classes.header}>404 – Page not found</h1>
+      <img
+        src="/logo-alpha-flat-outline.svg"
+        className={classes.logo}
+        alt="git-bug Logo"
+      />
+      <div className={classes.backLink}>
+        <BackToListButton />
+      </div>
+    </main>
+  );
+}
+
+export default NotFoundPage;

webui/src/themes/DefaultDark.ts 🔗

@@ -4,7 +4,8 @@ const defaultDarkTheme = createMuiTheme({
   palette: {
     type: 'dark',
     primary: {
-      main: '#263238',
+      dark: '#263238',
+      main: '#2a393e',
       light: '#525252',
     },
     error: {

webui/src/themes/DefaultLight.ts 🔗

@@ -4,8 +4,10 @@ const defaultLightTheme = createMuiTheme({
   palette: {
     type: 'light',
     primary: {
-      main: '#263238',
+      dark: '#263238',
+      main: '#5a6b73',
       light: '#f5f5f5',
+      contrastText: '#fff',
     },
     info: {
       main: '#e2f1ff',