Skip to content

Commit

Permalink
First complete version of the Admin UI
Browse files Browse the repository at this point in the history
closes #12
closes #8
  • Loading branch information
RobertoPrevato committed Jun 6, 2020
1 parent af22ca1 commit b79c9d9
Show file tree
Hide file tree
Showing 129 changed files with 3,414 additions and 3,190 deletions.
28 changes: 12 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ A GitHub bot and Web UI for managing contributor license agreements.
* Node.js v13.3.0
* yarn (install with `npm install -g yarn`)

Getting started:
A full setup requires also:

* a configured OAuth application in GitHub
* a configured GitHub application in GitHub
* an instance of EdgeDB with a database configured running `migrations/structure.edgeql`
* `.env` file populated with proper application settings
* a web hook for pull requests

For more information on this first-time configuration, refer to the documentation under `docs` folder.

### Getting started:

```bash
# install dependencies
Expand All @@ -17,31 +27,17 @@ yarn install
yarn next
```

A full setup requires configuring an EdgeDB database, two applications in GitHub, and a web hook for pull requests. For more information on a full

Running tests:

```
# tslint formatting
yarn tslint --project .
# unit tests
npm t
```

## Project structure
This project uses onion architecture, with the following namespaces:

* `constants` contains configuration constants
* `components` contains reusable React components
* `pages` is a folder handled by `Next.js`, with routes: pages and api
* `pages-common` is a folder containing common code for pages and api used in `Next.js` front-end
* `public` is a folder handled by `Next.js`, containing static files
* `service.domain` contains domain objects and interfaces for external services
* `service.data` contains concrete implementations of external services
* `service.handlers` contains business logic
* `docs` folder contains documentation for developers

Business logic is lousy coupled with the data access layer, since it is only aware of interfaces, not concrete implementations of DAL logic. Everything inside the `service` folder is abstracted from `Next.js` and should be reusable with other web frameworks, unmodified.

## GitHub integration
See the `docs` folder for examples and more information on the GitHub integration. Relevant functions in the code are commented to describe non obvious dynamics of the GitHub API. The folder includes a [Postman](https://www.postman.com) collection with some of the web requests used by this application.
149 changes: 72 additions & 77 deletions components/admin/administrators/administrator-new.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
import { Button, TextField } from "@material-ui/core";
import { Component, ReactElement } from "react";
import ErrorPanel, { ErrorProps } from "../../common/error"
import {Button, TextField} from "@material-ui/core";
import {Component, ReactElement} from "react";
import ErrorPanel, {ErrorProps} from "../../common/error";
import Preloader from "../../common/preloader";
import { changeHandler } from "../../forms"
import { post, ApplicationError } from "../../fetch";
import { validateEmail } from "../../../service/common/emails";

import {changeHandler} from "../../forms";
import {post, ApplicationError} from "../../fetch";
import {validateEmail} from "../../../service/common/emails";

interface NewAdministratorFormProps {
onNewAdministrator: () => void;
}


interface NewAdministratorFormState {
error?: ErrorProps
submitError?: ErrorProps
loading: boolean
submitting: boolean
email: string
emailError: boolean
emailHelperText: string
error?: ErrorProps;
submitError?: ErrorProps;
loading: boolean;
submitting: boolean;
email: string;
emailError: boolean;
emailHelperText: string;
}


export default class NewAdministratorForm
extends Component<NewAdministratorFormProps, NewAdministratorFormState> {

export default class NewAdministratorForm extends Component<
NewAdministratorFormProps,
NewAdministratorFormState
> {
constructor(props: NewAdministratorFormProps) {
super(props)
super(props);

this.state = {
error: undefined,
Expand All @@ -36,30 +34,29 @@ extends Component<NewAdministratorFormProps, NewAdministratorFormState> {
submitting: false,
email: "",
emailError: false,
emailHelperText: ""
}
emailHelperText: "",
};
}

validate(): boolean {
let anyError = false;
const {
email
} = this.state;
const {email} = this.state;

if (!email || !email.trim()) {
this.setState({
emailError: true,
emailHelperText: "Please insert a value"
})
emailHelperText: "Please insert a value",
});
anyError = true;
}

if (!validateEmail(email.trim())) {
this.setState({
emailError: true,
emailHelperText: "The value is not a valid email address. " +
"A single address is supported."
})
emailHelperText:
"The value is not a valid email address. " +
"A single address is supported.",
});
anyError = true;
}

Expand All @@ -73,69 +70,67 @@ extends Component<NewAdministratorFormProps, NewAdministratorFormState> {

this.setState({
submitting: true,
error: undefined
error: undefined,
});

const {
email
} = this.state;
const {email} = this.state;

post("/api/administrators", {
email: email.trim()
}).then(() => {
this.setState({
email: "",
submitting: false
});
email: email.trim(),
}).then(
() => {
this.setState({
email: "",
submitting: false,
});

this.props.onNewAdministrator();
}, (error: ApplicationError) => {
this.props.onNewAdministrator();
},
(error: ApplicationError) => {
if (error.status === 409) {
this.setState({
submitting: false,
emailError: true,
emailHelperText:
"There is already an administrator with this email.",
});
return;
}

if (error.status === 409) {
this.setState({
submitting: false,
emailError: true,
emailHelperText: "There is already an administrator with this email."
submitError: {},
});
return;
}

this.setState({
submitting: false,
submitError: {}
});
});
);
}

render(): ReactElement {
const state = this.state;

return (
<div>
{state.submitting && <Preloader className="overlay" />}
<h1>Add new administrator</h1>
<TextField
value={state.email}
error={state.emailError}
helperText={state.emailHelperText}
name="email"
required
fullWidth
label="Email"
autoFocus
autoComplete="off"
onChange={changeHandler.bind(this)}
/>
<div className="buttons-area">
<Button
key="submit-button"
onClick={() => this.submit()}
>
Submit
</Button>
<div>
{state.submitting && <Preloader className="overlay" />}
<h1>Add new administrator</h1>
<TextField
value={state.email}
error={state.emailError}
helperText={state.emailHelperText}
name="email"
required
fullWidth
label="Email"
autoFocus
autoComplete="off"
onChange={changeHandler.bind(this)}
/>
<div className="buttons-area">
<Button key="submit-button" onClick={() => this.submit()}>
Submit
</Button>
</div>
{state.submitError && <ErrorPanel {...state.submitError} />}
</div>
{state.submitError && <ErrorPanel {...state.submitError} />}
</div>
)
);
}
}
Loading

0 comments on commit b79c9d9

Please sign in to comment.