Merge pull request #538 from claudioantonio/webui_158

Michael Muré created

Webui - Allow to create bugs #158

Change summary

webui/package-lock.json              |   6 
webui/package.json                   |   2 
webui/src/App.tsx                    |   2 
webui/src/pages/bug/CommentInput.tsx |  96 +++++++++++++++++++++++
webui/src/pages/list/ListQuery.tsx   |  57 ++++++++++---
webui/src/pages/new/NewBug.graphql   |   7 +
webui/src/pages/new/NewBugPage.tsx   | 121 ++++++++++++++++++++++++++++++
7 files changed, 272 insertions(+), 19 deletions(-)

Detailed changes

webui/package-lock.json 🔗

@@ -17570,9 +17570,9 @@
       "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
     },
     "prettier": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz",
-      "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==",
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
+      "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
       "dev": true
     },
     "prettier-linter-helpers": {

webui/package.json 🔗

@@ -38,7 +38,7 @@
     "eslint-config-prettier": "^6.12.0",
     "eslint-plugin-graphql": "^4.0.0",
     "eslint-plugin-prettier": "^3.1.4",
-    "prettier": "^2.1.2"
+    "prettier": "^2.2.1"
   },
   "scripts": {
     "start": "npm run generate && react-scripts start",

webui/src/App.tsx 🔗

@@ -4,12 +4,14 @@ import { Route, Switch } from 'react-router';
 import Layout from './layout';
 import BugPage from './pages/bug';
 import ListPage from './pages/list';
+import NewBugPage from './pages/new/NewBugPage';
 
 export default function App() {
   return (
     <Layout>
       <Switch>
         <Route path="/" exact component={ListPage} />
+        <Route path="/new" exact component={NewBugPage} />
         <Route path="/bug/:id" exact component={BugPage} />
       </Switch>
     </Layout>

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

@@ -0,0 +1,96 @@
+import React, { useState, useEffect } from 'react';
+
+import Tab from '@material-ui/core/Tab';
+import Tabs from '@material-ui/core/Tabs';
+import TextField from '@material-ui/core/TextField';
+import { makeStyles } from '@material-ui/core/styles';
+
+import Content from 'src/components/Content';
+
+const useStyles = makeStyles((theme) => ({
+  container: {
+    margin: theme.spacing(2, 0),
+    padding: theme.spacing(0, 2, 2, 2),
+  },
+  textarea: {},
+  tabContent: {
+    margin: theme.spacing(2, 0),
+  },
+  preview: {
+    borderBottom: `solid 3px ${theme.palette.grey['200']}`,
+    minHeight: '5rem',
+  },
+  actions: {
+    display: 'flex',
+    justifyContent: 'flex-end',
+  },
+}));
+
+type TabPanelProps = {
+  children: React.ReactNode;
+  value: number;
+  index: number;
+} & React.HTMLProps<HTMLDivElement>;
+function TabPanel({ children, value, index, ...props }: TabPanelProps) {
+  return (
+    <div
+      role="tabpanel"
+      hidden={value !== index}
+      id={`editor-tabpanel-${index}`}
+      aria-labelledby={`editor-tab-${index}`}
+      {...props}
+    >
+      {value === index && children}
+    </div>
+  );
+}
+
+const a11yProps = (index: number) => ({
+  id: `editor-tab-${index}`,
+  'aria-controls': `editor-tabpanel-${index}`,
+});
+
+type Props = {
+  loading: boolean;
+  onChange: (comment: string) => void;
+};
+
+function CommentInput({ loading, onChange }: Props) {
+  const [input, setInput] = useState<string>('');
+  const [tab, setTab] = useState(0);
+  const classes = useStyles();
+
+  useEffect(() => {
+    onChange(input);
+  }, [input, onChange]);
+
+  return (
+    <div>
+      <Tabs value={tab} onChange={(_, t) => setTab(t)}>
+        <Tab label="Write" {...a11yProps(0)} />
+        <Tab label="Preview" {...a11yProps(1)} />
+      </Tabs>
+      <div className={classes.tabContent}>
+        <TabPanel value={tab} index={0}>
+          <TextField
+            fullWidth
+            label="Comment"
+            placeholder="Leave a comment"
+            className={classes.textarea}
+            multiline
+            value={input}
+            variant="filled"
+            rows="4" // TODO: rowsMin support
+            onChange={(e: any) => setInput(e.target.value)}
+            disabled={loading}
+          />
+        </TabPanel>
+        <TabPanel value={tab} index={1} className={classes.preview}>
+          <Content markdown={input} />
+        </TabPanel>
+      </div>
+    </div>
+  );
+}
+
+export default CommentInput;

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

@@ -2,6 +2,7 @@ import { ApolloError } from '@apollo/client';
 import React, { useState, useEffect, useRef } from 'react';
 import { useLocation, useHistory, Link } from 'react-router-dom';
 
+import { Button } from '@material-ui/core';
 import IconButton from '@material-ui/core/IconButton';
 import InputBase from '@material-ui/core/InputBase';
 import Paper from '@material-ui/core/Paper';
@@ -40,6 +41,17 @@ const useStyles = makeStyles<Theme, StylesProps>((theme) => ({
     alignItems: 'center',
     justifyContent: 'space-between',
   },
+  filterissueLabel: {
+    fontSize: '14px',
+    fontWeight: 'bold',
+    paddingRight: '12px',
+  },
+  filterissueContainer: {
+    display: 'flex',
+    flexDirection: 'row',
+    alignItems: 'flex-start',
+    justifyContents: 'left',
+  },
   search: {
     borderRadius: theme.shape.borderRadius,
     borderColor: fade(theme.palette.primary.main, 0.2),
@@ -95,6 +107,13 @@ const useStyles = makeStyles<Theme, StylesProps>((theme) => ({
       padding: theme.spacing(2, 3),
     },
   },
+  greenButton: {
+    backgroundColor: '#2ea44fd9',
+    color: '#fff',
+    '&:hover': {
+      backgroundColor: '#2ea44f',
+    },
+  },
 }));
 
 function editParams(
@@ -271,21 +290,29 @@ function ListQuery() {
   return (
     <Paper className={classes.main}>
       <header className={classes.header}>
-        <h1>Issues</h1>
-        <form onSubmit={formSubmit}>
-          <InputBase
-            placeholder="Filter"
-            value={input}
-            onInput={(e: any) => setInput(e.target.value)}
-            classes={{
-              root: classes.search,
-              focused: classes.searchFocused,
-            }}
-          />
-          <button type="submit" hidden>
-            Search
-          </button>
-        </form>
+        <div className="filterissueContainer">
+          <form onSubmit={formSubmit}>
+            <label className={classes.filterissueLabel} htmlFor="issuefilter">
+              Filter
+            </label>
+            <InputBase
+              id="issuefilter"
+              placeholder="Filter"
+              value={input}
+              onInput={(e: any) => setInput(e.target.value)}
+              classes={{
+                root: classes.search,
+                focused: classes.searchFocused,
+              }}
+            />
+            <button type="submit" hidden>
+              Search
+            </button>
+          </form>
+        </div>
+        <Button className={classes.greenButton} variant="contained" href="/new">
+          New issue
+        </Button>
       </header>
       <FilterToolbar query={query} queryLocation={queryLocation} />
       {content}

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

@@ -0,0 +1,121 @@
+import React, { FormEvent, useState } from 'react';
+
+import { Button } from '@material-ui/core';
+import Paper from '@material-ui/core/Paper';
+import TextField from '@material-ui/core/TextField/TextField';
+import { fade, makeStyles, Theme } from '@material-ui/core/styles';
+
+import CommentInput from '../bug/CommentInput';
+
+import { useNewBugMutation } from './NewBug.generated';
+
+/**
+ * Css in JS styles
+ */
+const useStyles = makeStyles((theme: Theme) => ({
+  main: {
+    maxWidth: 800,
+    margin: 'auto',
+    marginTop: theme.spacing(4),
+    marginBottom: theme.spacing(4),
+    padding: theme.spacing(2),
+    overflow: 'hidden',
+  },
+  titleInput: {
+    borderRadius: theme.shape.borderRadius,
+    borderColor: fade(theme.palette.primary.main, 0.2),
+    borderStyle: 'solid',
+    borderWidth: '1px',
+    backgroundColor: fade(theme.palette.primary.main, 0.05),
+    padding: theme.spacing(0, 0),
+    transition: theme.transitions.create([
+      'width',
+      'borderColor',
+      'backgroundColor',
+    ]),
+  },
+  form: {
+    display: 'flex',
+    flexDirection: 'column',
+  },
+  actions: {
+    display: 'flex',
+    justifyContent: 'flex-end',
+  },
+  gitbugButton: {
+    backgroundColor: '#2ea44fd9',
+    color: '#fff',
+    '&:hover': {
+      backgroundColor: '#2ea44f',
+    },
+  },
+}));
+
+/**
+ * Form to create a new issue
+ */
+function NewBugPage() {
+  const [newBug, { loading, error }] = useNewBugMutation();
+  const [issueTitle, setIssueTitle] = useState('');
+  const [issueComment, setIssueComment] = useState('');
+  const classes = useStyles();
+  let issueTitleInput: any;
+
+  function submitNewIssue(e: FormEvent) {
+    e.preventDefault();
+    if (!isFormValid()) return;
+    console.log('submitNewISsue');
+    console.log('title: ', issueTitle);
+    console.log('comment: ', issueComment);
+    newBug({
+      variables: {
+        input: {
+          title: issueTitle,
+          message: issueComment,
+        },
+      },
+    });
+    issueTitleInput.value = '';
+  }
+
+  function isFormValid() {
+    return issueTitle.length > 0 && issueComment.length > 0 ? true : false;
+  }
+
+  if (loading) return <div>Loading</div>;
+  if (error) return <div>Error</div>;
+
+  return (
+    <Paper className={classes.main}>
+      <form className={classes.form} onSubmit={submitNewIssue}>
+        <TextField
+          inputRef={(node) => {
+            issueTitleInput = node;
+          }}
+          label="Title"
+          className={classes.titleInput}
+          variant="outlined"
+          fullWidth
+          margin="dense"
+          onChange={(event: any) => setIssueTitle(event.target.value)}
+        />
+        <CommentInput
+          loading={false}
+          onChange={(comment: string) => setIssueComment(comment)}
+        />
+        <div className={classes.actions}>
+          <Button
+            className={classes.gitbugButton}
+            variant="contained"
+            type="submit"
+            disabled={isFormValid() ? false : true}
+          >
+            Submit new issue
+          </Button>
+        </div>
+      </form>
+    </Paper>
+  );
+}
+
+export default NewBugPage;