Compare commits
7 Commits
master
...
55a8b26a7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
55a8b26a7d
|
|||
|
4810a6ed74
|
|||
|
6da0c2b84a
|
|||
|
ea710c3845
|
|||
|
3998d43041
|
|||
|
d680d16a76
|
|||
|
b78929b566
|
@@ -17,6 +17,7 @@ parserOptions:
|
||||
plugins:
|
||||
- react
|
||||
- jest
|
||||
- testing-library
|
||||
rules:
|
||||
no-tabs: 0
|
||||
indent:
|
||||
|
||||
2
do.sh
2
do.sh
@@ -11,7 +11,7 @@ build() {
|
||||
|
||||
run() {
|
||||
echo "I am ${FUNCNAME[0]}ning"
|
||||
npx webpack serve
|
||||
API_ROOT=http://localhost:3000/api npx webpack serve
|
||||
}
|
||||
|
||||
fmt() {
|
||||
|
||||
1
jest/setEnvVars.js
Normal file
1
jest/setEnvVars.js
Normal file
@@ -0,0 +1 @@
|
||||
process.env.API_ROOT = "http://localhost:8080/";
|
||||
215
package-lock.json
generated
215
package-lock.json
generated
@@ -1105,6 +1105,16 @@
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs3": {
|
||||
"version": "7.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz",
|
||||
"integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js-pure": "^3.0.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz",
|
||||
@@ -1919,6 +1929,98 @@
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"@testing-library/dom": {
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz",
|
||||
"integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^4.2.0",
|
||||
"aria-query": "^4.2.2",
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.4",
|
||||
"lz-string": "^1.4.4",
|
||||
"pretty-format": "^26.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@testing-library/react": {
|
||||
"version": "11.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.3.tgz",
|
||||
"integrity": "sha512-BirBUGPkTW28ULuCwIbYo0y2+0aavHczBT6N9r3LrsswEW3pg25l1wgoE7I8QBIy1upXWkwKpYdWY7NYYP0Bxw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@testing-library/dom": "^7.28.1"
|
||||
}
|
||||
},
|
||||
"@testing-library/user-event": {
|
||||
"version": "12.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.2.tgz",
|
||||
"integrity": "sha512-4OsiTSo2vbQm+eOnm1un8b9i2Re4mn+D7d7ET6HXtzYKY7vPe3O01iYKRmSW9vS5mNrQcCLwvRhVq1gWs5YGKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
}
|
||||
},
|
||||
"@types/aria-query": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
|
||||
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz",
|
||||
@@ -2552,6 +2654,16 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"aria-query": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
|
||||
"integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"@babel/runtime-corejs3": "^7.10.2"
|
||||
}
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@@ -3574,6 +3686,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"core-js-pure": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz",
|
||||
"integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@@ -4001,6 +4119,12 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-accessibility-api": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz",
|
||||
"integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
@@ -4590,6 +4714,91 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-testing-library": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz",
|
||||
"integrity": "sha512-nQIFe2muIFv2oR2zIuXE4vTbcFNx8hZKRzgHZqJg8rfopIWwoTwtlbCCNELT/jXzVe1uZF68ALGYoDXjLczKiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "^3.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz",
|
||||
"integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/types": "3.10.1",
|
||||
"@typescript-eslint/typescript-estree": "3.10.1",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz",
|
||||
"integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz",
|
||||
"integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "3.10.1",
|
||||
"@typescript-eslint/visitor-keys": "3.10.1",
|
||||
"debug": "^4.1.1",
|
||||
"glob": "^7.1.6",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz",
|
||||
"integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
|
||||
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
|
||||
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -8100,6 +8309,12 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"lz-string": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
|
||||
"dev": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
|
||||
19
package.json
19
package.json
@@ -10,6 +10,9 @@
|
||||
"name": "Deepak Mallubhotla"
|
||||
},
|
||||
"jest": {
|
||||
"setupFiles": [
|
||||
"<rootDir>/jest/setEnvVars.js"
|
||||
],
|
||||
"reporters": [
|
||||
"default",
|
||||
"jest-junit"
|
||||
@@ -17,7 +20,13 @@
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"cobertura"
|
||||
]
|
||||
],
|
||||
"collectCoverage": true,
|
||||
"collectCoverageFrom": [
|
||||
"<rootDir>/src/**/*.jsx",
|
||||
"!<rootDir>/src/**/*.test.jsx"
|
||||
],
|
||||
"coverageProvider": "babel"
|
||||
},
|
||||
"private": "true",
|
||||
"license": "ISC",
|
||||
@@ -26,6 +35,8 @@
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
"@testing-library/react": "^11.2.3",
|
||||
"@testing-library/user-event": "^12.6.2",
|
||||
"babel-loader": "^8.2.2",
|
||||
"css-loader": "^5.0.1",
|
||||
"eslint": "^7.17.0",
|
||||
@@ -36,6 +47,7 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-testing-library": "^3.10.1",
|
||||
"jest": "^26.6.3",
|
||||
"jest-junit": "^12.0.0",
|
||||
"prettier": "2.2.1",
|
||||
@@ -50,5 +62,8 @@
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-hot-loader": "^4.13.0"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"since 2017-06"
|
||||
]
|
||||
}
|
||||
|
||||
20
src/App.jsx
20
src/App.jsx
@@ -1,19 +1,15 @@
|
||||
import React from "react";
|
||||
import { hot } from "react-hot-loader";
|
||||
import "./App.css";
|
||||
import AllPlansComponent from "./components/AllPlansComponent.jsx";
|
||||
import UnauthenticatedApp from "./components/UnauthenticatedApp";
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<h1> Hello, World! </h1>
|
||||
<div className="mainContent">
|
||||
<AllPlansComponent />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>Frontend</h1>
|
||||
<UnauthenticatedApp />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default hot(module)(App);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
.actionID,
|
||||
.actionDescription,
|
||||
.actionChunks {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.actionCompleted {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.actionChunks {
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.actionID {
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
.actionDescription {
|
||||
flex: 8;
|
||||
}
|
||||
|
||||
.actionWrapper {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from "react";
|
||||
import { action } from "../types";
|
||||
import "./Action.css";
|
||||
|
||||
class Action extends React.Component {
|
||||
render() {
|
||||
const action = this.props.action;
|
||||
const completed = "completed_on" in action;
|
||||
console.log([action.completed_on, completed]);
|
||||
return (
|
||||
<div className="actionWrapper">
|
||||
<div className="actionCompleted">{completed ? "X" : " "}</div>
|
||||
<div className="actionID">
|
||||
ID: {action.action_id} | {action.plan_id}
|
||||
</div>
|
||||
<div className="actionDescription">{action.action_description}</div>
|
||||
<div className="actionChunks">
|
||||
{action.completed_chunks}/{action.estimated_chunks}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Action.propTypes = {
|
||||
action: action,
|
||||
};
|
||||
|
||||
export default Action;
|
||||
@@ -1,3 +0,0 @@
|
||||
test("trying it out", () => {
|
||||
expect(true).toEqual(true);
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from "react";
|
||||
import Action from "./Action.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
import { action } from "../types";
|
||||
|
||||
class ActionsContainer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="ActionsListWrapper">
|
||||
<ul className="ActionsList">
|
||||
{this.props.actions.map((action) => {
|
||||
return (
|
||||
<li className="Action" key={action.action_id}>
|
||||
<Action action={action} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ActionsContainer.propTypes = {
|
||||
actions: PropTypes.arrayOf(action),
|
||||
};
|
||||
|
||||
export default ActionsContainer;
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from "react";
|
||||
import PlanList from "./PlanList.jsx";
|
||||
|
||||
class AllPlansComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
plans: [],
|
||||
};
|
||||
}
|
||||
|
||||
getPlans() {
|
||||
fetch("http://localhost:3000/api/plans", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.setState({
|
||||
plans: data,
|
||||
});
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getPlans();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="AllPlans">
|
||||
<PlanList plans={this.state.plans} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AllPlansComponent;
|
||||
13
src/components/Login-snapshot.test.jsx
Normal file
13
src/components/Login-snapshot.test.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import renderer from "react-test-renderer";
|
||||
import Login from "./Login";
|
||||
|
||||
test("Login Snapshot", () => {
|
||||
const loginFunc = jest.fn();
|
||||
|
||||
const login = renderer.create(<Login login={loginFunc} />);
|
||||
|
||||
const tree = login.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
44
src/components/Login.jsx
Normal file
44
src/components/Login.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default function Login({ login }) {
|
||||
const [username, setUserName] = useState();
|
||||
const [password, setPassword] = useState();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
login(username, password);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-wrapper">
|
||||
<h1>Please Log In</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label>
|
||||
<p>Username</p>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<p>Password</p>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Login.propTypes = {
|
||||
login: PropTypes.func.isRequired,
|
||||
};
|
||||
21
src/components/Login.test.jsx
Normal file
21
src/components/Login.test.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import Login from "./Login";
|
||||
|
||||
test("Login inputs", () => {
|
||||
const loginFunc = jest.fn();
|
||||
const login = render(<Login login={loginFunc} />);
|
||||
|
||||
const usernameInput = login.getByRole("textbox", { name: /Username/i });
|
||||
const passwordInput = login.getByLabelText("Password");
|
||||
const buttonInput = login.getByRole("button", { name: /Submit/i });
|
||||
|
||||
const username = "here's a username";
|
||||
const pw = "here's a password";
|
||||
|
||||
userEvent.type(usernameInput, username);
|
||||
userEvent.type(passwordInput, pw);
|
||||
userEvent.click(buttonInput);
|
||||
expect(loginFunc).toHaveBeenCalledWith(username, pw);
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from "react";
|
||||
import ActionsContainer from "./ActionsContainer.jsx";
|
||||
import { plan } from "../types";
|
||||
|
||||
class Plan extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
actions: [],
|
||||
};
|
||||
}
|
||||
|
||||
getActions() {
|
||||
fetch(
|
||||
"http://localhost:3000/api/actions?" +
|
||||
new URLSearchParams({
|
||||
plan_id: this.props.plan.plan_id,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.setState({
|
||||
actions: data,
|
||||
});
|
||||
})
|
||||
.catch((error) => console.error(error));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getActions();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="PlanActionsWrapper">
|
||||
<div>
|
||||
<h3>Plan for {this.props.plan.plan_date}</h3>
|
||||
</div>
|
||||
<ActionsContainer actions={this.state.actions.slice()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Plan.propTypes = {
|
||||
plan: plan,
|
||||
};
|
||||
|
||||
export default Plan;
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from "react";
|
||||
import Plan from "./Plan.jsx";
|
||||
import { plan } from "../types";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
class PlanList extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="PlanListWrapper">
|
||||
<ul className="PlanList">
|
||||
{this.props.plans.map((plan) => {
|
||||
return (
|
||||
<li className="Plan" key={plan.plan_id}>
|
||||
<Plan plan={plan} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PlanList.propTypes = {
|
||||
plans: PropTypes.arrayOf(plan),
|
||||
};
|
||||
|
||||
export default PlanList;
|
||||
53
src/components/Register.jsx
Normal file
53
src/components/Register.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default function Register({ register }) {
|
||||
const [username, setUserName] = useState();
|
||||
const [displayName, setDisplayName] = useState();
|
||||
const [password, setPassword] = useState();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
register(username, displayName, password);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="register-wrapper">
|
||||
<h1>Enter your data to register.</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label>
|
||||
<p>Username</p>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<p>Name</p>
|
||||
<input
|
||||
id="displayName"
|
||||
type="text"
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<p>Password</p>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<button type="submit">Sign me up</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Register.propTypes = {
|
||||
register: PropTypes.func.isRequired,
|
||||
};
|
||||
30
src/components/UnauthenticatedApp.jsx
Normal file
30
src/components/UnauthenticatedApp.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { useState } from "react";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import Login from "./Login";
|
||||
import Register from "./Register";
|
||||
|
||||
function UnauthenticatedApp() {
|
||||
const { login, register } = useAuth();
|
||||
|
||||
const [showRegister, setShowRegister] = useState(false);
|
||||
|
||||
function toggle() {
|
||||
setShowRegister(!showRegister);
|
||||
}
|
||||
|
||||
const form = showRegister ? (
|
||||
<Register register={register} />
|
||||
) : (
|
||||
<Login login={login} />
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<button type="submit" onClick={toggle}>
|
||||
toggle signup
|
||||
</button>
|
||||
{form}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnauthenticatedApp;
|
||||
42
src/components/__snapshots__/Login-snapshot.test.jsx.snap
Normal file
42
src/components/__snapshots__/Login-snapshot.test.jsx.snap
Normal file
@@ -0,0 +1,42 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Login Snapshot 1`] = `
|
||||
<div
|
||||
className="login-wrapper"
|
||||
>
|
||||
<h1>
|
||||
Please Log In
|
||||
</h1>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<label>
|
||||
<p>
|
||||
Username
|
||||
</p>
|
||||
<input
|
||||
id="username"
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<p>
|
||||
Password
|
||||
</p>
|
||||
<input
|
||||
id="password"
|
||||
onChange={[Function]}
|
||||
type="password"
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
18
src/context/AuthContext.jsx
Normal file
18
src/context/AuthContext.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import { register, login } from "../services/auth-service";
|
||||
|
||||
const AuthContext = React.createContext();
|
||||
|
||||
const AuthProvider = (props) => {
|
||||
return <AuthContext.Provider value={{ login, register }} {...props} />;
|
||||
};
|
||||
|
||||
function useAuth() {
|
||||
const context = React.useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(`useAuth must be used within a AuthProvider`);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export { AuthProvider, useAuth };
|
||||
13
src/context/index.jsx
Normal file
13
src/context/index.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { AuthProvider } from "./AuthContext.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
function AppProviders({ children }) {
|
||||
return <AuthProvider>{children}</AuthProvider>;
|
||||
}
|
||||
|
||||
export default AppProviders;
|
||||
|
||||
AppProviders.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
@@ -1,4 +1,11 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App.jsx";
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
import AppProviders from "./context";
|
||||
|
||||
ReactDOM.render(
|
||||
<AppProviders>
|
||||
<App />
|
||||
</AppProviders>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
50
src/services/auth-service.js
Normal file
50
src/services/auth-service.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { API_ROOT } from "./config";
|
||||
|
||||
export const register = (username, displayName, password) => {
|
||||
const url = API_ROOT + "auth/register";
|
||||
|
||||
const body = {
|
||||
username: username,
|
||||
display_name: displayName,
|
||||
password: password,
|
||||
};
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
};
|
||||
|
||||
export const login = (username, password) => {
|
||||
const url = API_ROOT + "auth/tokens";
|
||||
|
||||
const body = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data.token) {
|
||||
console.log(data.token);
|
||||
return data.token;
|
||||
}
|
||||
throw Error("where's my token");
|
||||
});
|
||||
};
|
||||
1
src/services/config.jsx
Normal file
1
src/services/config.jsx
Normal file
@@ -0,0 +1 @@
|
||||
export const API_ROOT = process.env.API_ROOT;
|
||||
5
src/services/config.test.jsx
Normal file
5
src/services/config.test.jsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { API_ROOT } from "./config";
|
||||
|
||||
test("testing config api root", () => {
|
||||
expect(API_ROOT).toEqual("http://localhost:8080/");
|
||||
});
|
||||
@@ -36,5 +36,10 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.EnvironmentPlugin({
|
||||
API_ROOT: "http://localhost:8080/"
|
||||
})
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user