Added React components and pages

This commit is contained in:
Andrew Scott 2022-06-04 01:22:40 -04:00
parent 7ed98cb838
commit 52f2a1fdc9
Signed by: a
GPG key ID: 3EB62D0BBB8DB381
22 changed files with 25699 additions and 0 deletions

23
exercises-ui/.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

25193
exercises-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

41
exercises-ui/package.json Normal file
View file

@ -0,0 +1,41 @@
{
"name": "exercises-ui",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:3000",
"dependencies": {
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
exercises-ui/src/App.css Normal file
View file

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

28
exercises-ui/src/App.js Normal file
View file

@ -0,0 +1,28 @@
import "./App.css";
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import HomePage from "./pages/HomePage";
import CreatePage from "./pages/CreatePage";
import EditPage from "./pages/EditPage";
function App() {
return (
<div className="App">
<Router>
<div className="App-header">
<Route path="/" exact>
<HomePage />
</Route>
<Route path="/create">
<CreatePage />
</Route>
<Route path="/edit">
<EditPage />
</Route>
</div>
</Router>
</div>
);
}
export default App;

View file

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View file

@ -0,0 +1,22 @@
import React from "react";
import { MdDeleteForever, MdEdit } from "react-icons/md";
function Exercise({ exercise, onDelete, onEdit }) {
return (
<tr>
<td>{exercise.name}</td>
<td>{exercise.reps}</td>
<td>{exercise.weight}</td>
<td>{exercise.unit}</td>
<td>{exercise.date}</td>
<td>
<MdEdit onClick={() => onEdit(exercise._id)} />
</td>
<td>
<MdDeleteForever onClick={() => onDelete(exercise._id)} />
</td>
</tr>
);
}
export default Exercise;

View file

@ -0,0 +1,27 @@
import React from "react";
import Exercise from "./Exercise";
function ExerciseList({ exercises, onDelete, onEdit }) {
return (
<table id="exercises">
<thead>
<tr>
<th>Name</th>
<th>Reps</th>
<th>Weight</th>
<th>Unit</th>
<th>Date</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{exercises.map((exercise, i) => (
<Exercise exercise={exercise} onDelete={onDelete} key={i} />
))}
</tbody>
</table>
);
}
export default ExerciseList;

View file

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

17
exercises-ui/src/index.js Normal file
View file

@ -0,0 +1,17 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,67 @@
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import "../App.css";
export const CreatePage = () => {
const [name, setName] = useState("");
const [reps, setReps] = useState("");
const [weight, setWeight] = useState("");
const [unit, setUnit] = useState("");
const [date, setDate] = useState("");
const history = useHistory();
const createExercise = async () => {
const newExercise = { name, reps, weight, unit, date };
const response = await fetch("/exercises", {
method: "POST",
body: JSON.stringify(newExercise),
headers: { "Content-Type": "application/json" },
});
if (response.status === 201) {
alert("Successfully created exercise");
} else {
alert(`Failed to add exercise, status code = ${response.status}`);
}
history.push("/");
};
return (
<div className="App">
<h2>Create Exercise</h2>
<input
type="text"
placeholder="Enter name here"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="number"
placeholder="Enter reps here"
value={reps}
onChange={(e) => setReps(e.target.value)}
/>
<input
type="number"
placeholder="Enter weight here"
value={weight}
onChange={(e) => setWeight(e.target.value)}
/>
<input
type="text"
placeholder="Enter unit here"
value={unit}
onChange={(e) => setUnit(e.target.value)}
/>
<input
type="text"
placeholder="Enter date here"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<button onClick={createExercise}>Create</button>
</div>
);
};
export default CreatePage;

View file

@ -0,0 +1,81 @@
import "../App.css";
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
export const EditPage = () => {
const [name, setName] = useState("");
const [reps, setReps] = useState("");
const [weight, setWeight] = useState("");
const [unit, setUnit] = useState("");
const [date, setDate] = useState("");
const history = useHistory();
const editExercise = async () => {
const updatedExercise = { name, reps, weight, unit, date };
const response = await fetch(`/exercises/id`, {
method: "PUT",
body: JSON.stringify(updatedExercise),
headers: { "Content-Type": "application/json" },
});
if (response.status === 200) {
alert("Successfully edited exercise");
} else {
alert(`Failed to edit exercise, status code = ${response.status}`);
}
history.push("/");
};
return (
<div className="App">
<h2>Edit Exercise</h2>
<input
type="text"
placeholder="Enter name here"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="number"
placeholder="Enter reps here"
value={reps}
onChange={(e) => setReps(e.target.value)}
/>
<input
type="number"
placeholder="Enter weight here"
value={weight}
onChange={(e) => setWeight(e.target.value)}
/>
<input
type="text"
placeholder="Enter unit here"
value={unit}
onChange={(e) => setUnit(e.target.value)}
/>
<input
type="text"
placeholder="Enter date here"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<button onClick={editExercise}>Edit</button>
</div>
);
};
// const response = await fetch(`/exercises/${exerciseToEdit._id}`, {
// method: 'PUT',
// body: JSON.stringify({ name: name, reps: reps, weight: weight, unit: unit, date: date }),
// headers: {
// 'Content-Type': 'application/json',
// },
// });
// if(response.status === 200){
// alert("Successfully edited the exercise!");
// } else {
// alert(`Failed to edit the exercise, status code = ${response.status}`);
// }
// };
export default EditPage;

View file

@ -0,0 +1,51 @@
import ExerciseList from "../components/ExerciseList";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useHistory } from "react-router-dom";
import "../App.css";
function HomePage() {
const [exercises, setExercises] = useState([]);
const onDelete = async (_id) => {
const response = await fetch(`/exercises/${_id}`, { method: "DELETE" });
if (response.status === 204) {
setExercises(exercises.filter((m) => m._id !== _id));
} else {
console.error(
`Failed to delete exercise with _id = ${_id}, status code = ${response.status}`
);
}
};
const onEdit = async (exerciseToEdit) => {
//setExerciseToEdit(exerciseToEdit);
//history.push("/edit");
};
const loadExercises = async () => {
const response = await fetch("/exercises");
const data = await response.json();
setExercises(data);
};
useEffect(() => {
loadExercises();
}, []);
return (
<div className="App">
<h2>List of Exercises</h2>
<ExerciseList
exercises={exercises}
onDelete={onDelete}
onEdit={onEdit}
></ExerciseList>
<div className="App-link">
<Link to="/create">Create</Link>
</div>
</div>
);
}
export default HomePage;

View file

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View file

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';