Finished login component

Did the following:
 - Extract front-end from component;
 - Added localStorage for remembering API details and access key;
 - Added routing to login component;

Todo:
 - Remove getApi*FromDTO;
 - Create ProtectedRoute to prevent unauthorized entry to /dashboard;
This commit is contained in:
2020-02-19 00:48:30 +01:00
parent 60079a1417
commit ae7d63db35
9 changed files with 254 additions and 62 deletions
+18 -7
View File
@@ -1,4 +1,4 @@
[![Netlify Status](https://api.netlify.com/api/v1/badges/9527e4dc-1bb2-40ef-b6eb-8515b91579bf/deploy-status)](https://app.netlify.com/sites/upbeat-agnesi-aad3fb/deploys)
[![Netlify Status](https://api.netlify.com/api/v1/badges/9527e4dc-1bb2-40ef-b6eb-8515b91579bf/deploy-status)](https://upbeat-agnesi-aad3fb.netlify.com/)
## Available Scripts
@@ -9,15 +9,26 @@ In the project directory, you can run:
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
The page will reload if you make edits, you will also see any lint errors in the console.
### `yarn build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
The build is minified and the filenames include the hashes. Your app is ready to be deployed!
### Requirement list
- [X] Create React web app
- [ ] It should have fields for giving start date, end date and access token as arguments to the app.
- [ ] App should make HTTP GET request to the API to fetch chat counts between those two given dates.
- [ ] The dashboard should render three values from the API.
- [ ] The dashboard should render a Paginated List.
### Software used
\# | Software | Reason
--- | --- | ---
1. | React | Requirement.
2. | React Router | To route between different components, a react router is used.
3. | TypeScript | To make use of the strong types, I made use of TypeScript
4. | Bootstrap | To structure the front-end, bootstrap is used.
+6
View File
@@ -3,6 +3,9 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-solid-svg-icons": "^5.12.1",
"@fortawesome/react-fontawesome": "^0.1.8",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.1.2",
@@ -13,13 +16,16 @@
"@types/prop-types": "^15.7.3",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"@types/react-fontawesome": "^1.6.4",
"@types/react-router-dom": "^5.1.3",
"axios": "^0.19.2",
"bootstrap": "^4.4.1",
"jquery": "^3.4.1",
"popper.js": "^1.16.1",
"react": "^16.12.0",
"react-bootstrap": "^1.0.0-beta.16",
"react-dom": "^16.12.0",
"react-fontawesome": "^1.7.1",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.1",
"typescript": "~3.7.2"
+1
View File
@@ -8,6 +8,7 @@
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Demo App - Jeroen Vijgen</title>
</head>
+4 -8
View File
@@ -6,18 +6,14 @@ import Login from '../LoginComponent/Login';
import Dashboard from '../DashboardComponent/Dashboard';
export default class App extends React.Component {
componentDidMount() {
if((localStorage.getItem("APILink") !== undefined || localStorage.getItem("APILink") !== '') &&
(localStorage.getItem("AccessToken") !== undefined || localStorage.getItem("AccessToken") !== '')) {
return <Redirect to="/dashboard" />
};
}
render() {
return (
<Switch>
<Route exact path="/" component={Login} />
<Route path="/dashboard" component={Dashboard} />
<Route exact path="/"
component={Login} />
<Route path="/dashboard"
component={Dashboard} />
<Redirect to="/" />
</Switch>
);
+52 -35
View File
@@ -1,44 +1,61 @@
import React from 'react';
import { Card, Button, InputGroup, FormControl } from 'react-bootstrap';
import LoginView from '../../Views/LoginView';
import API_DTO from '../../DTOs/api.dto';
export default class Login extends React.Component<{
history: any
}, {
dto: any
}> {
export default class Login extends React.Component {
// eslint-disable-next-line
constructor(props: any) {
super(props);
this.persistApiLink = this.persistApiLink.bind(this);
this.persistAccessToken = this.persistAccessToken.bind(this);
this.verifyLogin = this.verifyLogin.bind(this);
this.getApiLinkFromDTO = this.getApiLinkFromDTO.bind(this);
this.getApiTokenFromDTO = this.getApiTokenFromDTO.bind(this);
}
render () {
return (
<div className="LoginComponent">
<Card className="text-center">
<Card.Body>
<Card.Title>Please login to see the dashboard.</Card.Title>
<Card.Text>
Please fill in the following items: <br /><br />
<InputGroup className="mb-3">
<InputGroup.Prepend>
<InputGroup.Text id="inputGroup-sizing-default">API url</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
aria-label="Default"
aria-describedby="inputGroup-sizing-default"
/>
</InputGroup>
<InputGroup className="mb-3">
<InputGroup.Prepend>
<InputGroup.Text id="inputGroup-sizing-default">API token</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
aria-label="Default"
aria-describedby="inputGroup-sizing-default"
/>
</InputGroup>
</Card.Text>
<Button variant="primary">Login</Button>
</Card.Body>
<Card.Footer className="text-muted">Your last login was X-X-X @ XX:XX</Card.Footer>
</Card>
</div>
);
componentDidMount() {
const DTO = new API_DTO();
DTO.setApiLink(localStorage.getItem("APILink"));
DTO.setApiToken(localStorage.getItem("AccessToken"));
this.setState({ dto: DTO });
}
verifyLogin() {
console.log("API Link: " + this.state.dto.getApiLink());
console.log("API Token: " + this.state.dto.getApiToken());
localStorage.setItem("lastLoggedIn", new Date().toUTCString());
// eslint-disable-next-line
this.props.history.push('/dashboard');
};
getApiLinkFromDTO(): string {
return this.state.dto.getApiLink();
}
getApiTokenFromDTO(): string {
return this.state.dto.getApiToken();
}
persistApiLink(e: any): void {
this.state.dto.setApiLink(e.target.value);
}
persistAccessToken(e: any): void {
this.state.dto.setApiToken(e.target.value);
}
render() {
return (<LoginView
persistApiLink={this.persistApiLink}
persistAccessToken={this.persistAccessToken}
verifyLogin={this.verifyLogin}
getApiLinkFromDTO={this.getApiLinkFromDTO}
getApiTokenFromDTO={this.getApiTokenFromDTO}
/>);
}
}
+29
View File
@@ -0,0 +1,29 @@
export default class API_DTO {
API_Link: string;
API_Token: string;
constructor() {
this.API_Link = "";
this.API_Token = "";
}
setApiLink(_api_link: string | null): void {
if (_api_link === null) _api_link = "";
this.API_Link = _api_link;
localStorage.setItem("APILink", this.API_Link);
}
setApiToken(_api_token: string | null): void {
if (_api_token === null) _api_token = "";
this.API_Token = _api_token;
localStorage.setItem("AccessToken", this.API_Token);
}
getApiLink() {
return this.API_Link;
}
getApiToken() {
return this.API_Token;
}
}
+44
View File
@@ -0,0 +1,44 @@
import React from 'react';
import { Card, Button, FormControl } from 'react-bootstrap';
export default class LoginView extends React.Component<{
persistApiLink: any,
persistAccessToken: any,
verifyLogin: any,
getApiLinkFromDTO: any,
getApiTokenFromDTO: any
}> {
render () {
return (
<div className="LoginComponent">
<Card className="text-center">
<Card.Body>
<Card.Title>Please login to see the dashboard.</Card.Title>
<Card.Text>
Please fill in the following items: <br /><br />
<FormControl aria-label="Default"
aria-describedby="inputGroup-sizing-default"
type="text"
placeholder="API Link"
defaultValue={localStorage.getItem("APILink") || ""}
onChange={(e: any) => this.props.persistApiLink(e)}
/>
<FormControl
aria-label="Default"
aria-describedby="inputGroup-sizing-default"
type="text"
placeholder="API Token"
defaultValue={localStorage.getItem("AccessToken") || ""}
onChange={(e: any) => this.props.persistAccessToken(e)}
/>
</Card.Text>
<Button variant="primary" onClick={this.props.verifyLogin}>Login </Button>
</Card.Body>
{ localStorage.getItem("lastLoggedIn") !== null &&
<Card.Footer className="text-muted">Your last login was { localStorage.getItem("lastLoggedIn") }</Card.Footer>
}
</Card>
</div>
);
}
}
+5 -1
View File
@@ -4,4 +4,8 @@ import './index.css';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './Components/AppComponent/App';
ReactDOM.render(<Router><App /></Router>, document.getElementById('App'));
ReactDOM.render(
<Router >
<App />
</Router>,
document.getElementById('App'));
+95 -11
View File
@@ -1259,6 +1259,43 @@ __metadata:
languageName: node
linkType: hard
"@fortawesome/fontawesome-common-types@npm:^0.2.27":
version: 0.2.27
resolution: "@fortawesome/fontawesome-common-types@npm:0.2.27"
checksum: 2eb1f4adbe31a6533b3d3fdd20e37fd7c9e61cf29d144fcb3d225a5d16b57c16fa2a72dd6862382ea7b184aca85fd65b66781de1570a4b911eb4e9b39a74f9de
languageName: node
linkType: hard
"@fortawesome/fontawesome-svg-core@npm:^1.2.27":
version: 1.2.27
resolution: "@fortawesome/fontawesome-svg-core@npm:1.2.27"
dependencies:
"@fortawesome/fontawesome-common-types": ^0.2.27
checksum: 000027a6d1a33a152e4799bd0c3c1507205697f86ddc0961f69f83c8561bcf93327353980efc8794f1dd7e7a1a50a36f710198d9f7e03855d9832fd5df6958f9
languageName: node
linkType: hard
"@fortawesome/free-solid-svg-icons@npm:^5.12.1":
version: 5.12.1
resolution: "@fortawesome/free-solid-svg-icons@npm:5.12.1"
dependencies:
"@fortawesome/fontawesome-common-types": ^0.2.27
checksum: f966c13f826dfe57b2e2a212d5968ce7d4f9670ffb26d675259a2f0d19dfa777d269e6e4506e16d00da34943ae8526eb1fb2b889467f309ca3dbf371a57aec94
languageName: node
linkType: hard
"@fortawesome/react-fontawesome@npm:^0.1.8":
version: 0.1.8
resolution: "@fortawesome/react-fontawesome@npm:0.1.8"
dependencies:
prop-types: ^15.5.10
peerDependencies:
"@fortawesome/fontawesome-svg-core": ^1.2.20
react: 16.x
checksum: c13777b29061dcca6d39ee2f30d820e1221e4293ce87d336197bb6e0c19adbd4bbb2963be03c71f8e093a36e813b8acf2079849e62a7450f1d871a8678ecd5d6
languageName: node
linkType: hard
"@hapi/address@npm:2.x.x":
version: 2.1.4
resolution: "@hapi/address@npm:2.1.4"
@@ -1769,7 +1806,7 @@ __metadata:
languageName: node
linkType: hard
"@types/history@npm:*":
"@types/history@npm:*, @types/history@npm:^4.7.5":
version: 4.7.5
resolution: "@types/history@npm:4.7.5"
checksum: 16a19c2b1aee6d0ff055940e4ca29db26aa8b0c8ff69161e70dec2151abb4fe3d5be846577c696a5d870a281e362669707ff43323d435a3d84811e6a238348f1
@@ -1876,6 +1913,15 @@ __metadata:
languageName: node
linkType: hard
"@types/react-fontawesome@npm:^1.6.4":
version: 1.6.4
resolution: "@types/react-fontawesome@npm:1.6.4"
dependencies:
"@types/react": "*"
checksum: ccf31aafa5c595ae9736ce5a9942c141a75c9ad11f8977c62ed4b06d7e1961ee8f226257fe867bd6df72ee62eed5bf8552250ada06d2fe38f8988a4a775fc0f6
languageName: node
linkType: hard
"@types/react-router-dom@npm:^5.1.3":
version: 5.1.3
resolution: "@types/react-router-dom@npm:5.1.3"
@@ -2817,6 +2863,15 @@ __metadata:
languageName: node
linkType: hard
"axios@npm:^0.19.2":
version: 0.19.2
resolution: "axios@npm:0.19.2"
dependencies:
follow-redirects: 1.5.10
checksum: 28c860c203dc1e545a899eea83c95c51bb7de4a21ffef4781de11331207371c9cb1a680bcc7122dda936cd5e51f83f7b0a0eccc50801229b5e4914c8f2dd8907
languageName: node
linkType: hard
"axobject-query@npm:^2.0.2":
version: 2.1.1
resolution: "axobject-query@npm:2.1.1"
@@ -4530,6 +4585,15 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:=3.1.0":
version: 3.1.0
resolution: "debug@npm:3.1.0"
dependencies:
ms: 2.0.0
checksum: 31ac62be845b3e2bc49ea566665749e58030123e6d0b2960cb7c730037dc825f46d012e3f63f43fc9d60de3c52edbce705d22cb7e571e2822c81bc5d5ae09da9
languageName: node
linkType: hard
"debug@npm:^3.0.0, debug@npm:^3.1.1, debug@npm:^3.2.5, debug@npm:^3.2.6":
version: 3.2.6
resolution: "debug@npm:3.2.6"
@@ -4670,9 +4734,13 @@ __metadata:
version: 0.0.0-use.local
resolution: "demo-app@workspace:."
dependencies:
"@fortawesome/fontawesome-svg-core": ^1.2.27
"@fortawesome/free-solid-svg-icons": ^5.12.1
"@fortawesome/react-fontawesome": ^0.1.8
"@testing-library/jest-dom": ^4.2.4
"@testing-library/react": ^9.4.0
"@testing-library/user-event": ^7.1.2
"@types/history": ^4.7.5
"@types/istanbul-lib-coverage": ^2.0.1
"@types/jest": ^24.9.1
"@types/mocha": ^7.0.1
@@ -4680,23 +4748,26 @@ __metadata:
"@types/prop-types": ^15.7.3
"@types/react": ^16.9.19
"@types/react-dom": ^16.9.5
"@types/react-fontawesome": ^1.6.4
"@types/react-router-dom": ^5.1.3
"@typescript-eslint/eslint-plugin": ^2.19.2
"@typescript-eslint/parser": ^2.19.2
axios: ^0.19.2
bootstrap: ^4.4.1
eslint: ^6.8.0
eslint-config-prettier: ^6.10.0
eslint-plugin-prettier: ^3.1.2
eslint-plugin-react: ^7.18.3
history: ^4.10.1
jquery: ^3.4.1
popper.js: ^1.16.1
prettier: ^1.19.1
react: ^16.12.0
react-bootstrap: ^1.0.0-beta.16
react-dom: ^16.12.0
react-fontawesome: ^1.7.1
react-router-dom: ^5.1.2
react-scripts: 3.3.1
store: ^2.0.12
typescript: ~3.7.2
languageName: unknown
linkType: soft
@@ -5994,6 +6065,15 @@ __metadata:
languageName: node
linkType: hard
"follow-redirects@npm:1.5.10":
version: 1.5.10
resolution: "follow-redirects@npm:1.5.10"
dependencies:
debug: =3.1.0
checksum: 23c6333d60b73b899f9495d67b3dd5b2b99c26cd76278d03def3c39c715f0eed53cbfe1eb1e342a7281041961fe2c69c03daa97279df66a552ab6fb3d22059fc
languageName: node
linkType: hard
"follow-redirects@npm:^1.0.0":
version: 1.10.0
resolution: "follow-redirects@npm:1.10.0"
@@ -6582,7 +6662,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"history@npm:^4.9.0":
"history@npm:^4.10.1, history@npm:^4.9.0":
version: 4.10.1
resolution: "history@npm:4.10.1"
dependencies:
@@ -11118,7 +11198,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2":
"prop-types@npm:^15.5.10, prop-types@npm:^15.5.6, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2":
version: 15.7.2
resolution: "prop-types@npm:15.7.2"
dependencies:
@@ -11425,6 +11505,17 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"react-fontawesome@npm:^1.7.1":
version: 1.7.1
resolution: "react-fontawesome@npm:1.7.1"
dependencies:
prop-types: ^15.5.6
peerDependencies:
react: ">=0.12.0"
checksum: 9cedcd84255cb13196e7db70d49e928dc26982d059ae72396171f132933a38f50c9454b93f1f127edfd4a7fc7a4d01611f82dd73e86110724884ae9e9c064596
languageName: node
linkType: hard
"react-is@npm:^16.3.2, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.4":
version: 16.12.0
resolution: "react-is@npm:16.12.0"
@@ -12833,13 +12924,6 @@ resolve@1.1.7:
languageName: node
linkType: hard
"store@npm:^2.0.12":
version: 2.0.12
resolution: "store@npm:2.0.12"
checksum: 101053f2842b6a186d8556ef5a176feb4494cfe4a182b454c5c7150362c67344abafd8e012c7769ea51745cdff38447c3e0c9d5ef36740bcc65e174adb605309
languageName: node
linkType: hard
"stream-browserify@npm:^2.0.1":
version: 2.0.2
resolution: "stream-browserify@npm:2.0.2"