diff --git a/backend/.env b/backend/.env index 7653e6e..64c53a3 100644 --- a/backend/.env +++ b/backend/.env @@ -1,2 +1,2 @@ PORT=4000 -MONGO_URI=mongodb+srv://mario:test12345@mernapp.2susood.mongodb.net/merndb?retryWrites=true&w=majority \ No newline at end of file +MONGO_URI=mongodb+srv://Feelings:MN123456@mernapp.qv7xqpq.mongodb.net/?retryWrites=true&w=majority \ No newline at end of file diff --git a/backend/controllers/workoutController.js b/backend/controllers/workoutController.js index c1af009..9abd54b 100644 --- a/backend/controllers/workoutController.js +++ b/backend/controllers/workoutController.js @@ -3,7 +3,7 @@ const mongoose = require('mongoose') // get all workouts const getWorkouts = async (req, res) => { - const workouts = await Workout.find({}).sort({createdAt: -1}) + const workouts = await Workout.find({}).sort({ createdAt: -1 }) res.status(200).json(workouts) } @@ -13,13 +13,13 @@ const getWorkout = async (req, res) => { const { id } = req.params if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(404).json({error: 'No such workout'}) + return res.status(404).json({ error: 'No such workout' }) } const workout = await Workout.findById(id) if (!workout) { - return res.status(404).json({error: 'No such workout'}) + return res.status(404).json({ error: 'No such workout' }) } res.status(200).json(workout) @@ -27,7 +27,24 @@ const getWorkout = async (req, res) => { // create a new workout const createWorkout = async (req, res) => { - const {title, load, reps} = req.body + const { title, load, reps } = req.body + + let emptyFields = [] + + if (!title) { + emptyFields.push('title') + } + if (!load) { + emptyFields.push('load') + } + if (!reps) { + emptyFields.push('reps') + } + if (emptyFields.length > 0) { + return res + .status(400) + .json({ error: 'Please fill in missing fields', emptyFields }) + } // add to the database try { @@ -43,13 +60,13 @@ const deleteWorkout = async (req, res) => { const { id } = req.params if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(400).json({error: 'No such workout'}) + return res.status(400).json({ error: 'No such workout' }) } - const workout = await Workout.findOneAndDelete({_id: id}) + const workout = await Workout.findOneAndDelete({ _id: id }) - if(!workout) { - return res.status(400).json({error: 'No such workout'}) + if (!workout) { + return res.status(400).json({ error: 'No such workout' }) } res.status(200).json(workout) @@ -60,15 +77,18 @@ const updateWorkout = async (req, res) => { const { id } = req.params if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(400).json({error: 'No such workout'}) + return res.status(400).json({ error: 'No such workout' }) } - const workout = await Workout.findOneAndUpdate({_id: id}, { - ...req.body - }) + const workout = await Workout.findOneAndUpdate( + { _id: id }, + { + ...req.body, + }, + ) if (!workout) { - return res.status(400).json({error: 'No such workout'}) + return res.status(400).json({ error: 'No such workout' }) } res.status(200).json(workout) @@ -79,5 +99,5 @@ module.exports = { getWorkout, createWorkout, deleteWorkout, - updateWorkout -} \ No newline at end of file + updateWorkout, +} diff --git a/backend/package.json b/backend/package.json index 75eb2bb..c00e2bb 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,11 +1,11 @@ { "name": "backend", "version": "1.0.0", - "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "nodemon server.js" + "dev": "nodemon server.js", + "start": "node server.js" }, "keywords": [], "author": "", @@ -14,5 +14,6 @@ "dotenv": "^16.0.1", "express": "^4.18.1", "mongoose": "^6.3.5" - } + }, + "description": "" } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e75661a..8148572 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "date-fns": "^2.29.1", "react": "^18.1.0", "react-dom": "^18.1.0", "react-router-dom": "^6.3.0", @@ -6036,6 +6037,18 @@ "node": ">=10" } }, + "node_modules/date-fns": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.1.tgz", + "integrity": "sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw==", + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -20654,6 +20667,11 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.1.tgz", + "integrity": "sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index bda3474..8d5b4f0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", + "date-fns": "^2.29.1", "react": "^18.1.0", "react-dom": "^18.1.0", "react-router-dom": "^6.3.0", diff --git a/frontend/public/index.html b/frontend/public/index.html index aa069f2..5851d01 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -24,6 +24,8 @@ 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`. --> + + React App diff --git a/frontend/src/components/WorkoutDetails.js b/frontend/src/components/WorkoutDetails.js index 48c4391..0834ba0 100644 --- a/frontend/src/components/WorkoutDetails.js +++ b/frontend/src/components/WorkoutDetails.js @@ -1,13 +1,40 @@ +import { useWorkoutsContext } from '../hooks/useWorkoutsContext' +// date fns +import formatDistanceToNow from 'date-fns/formatDistanceToNow' + const WorkoutDetails = ({ workout }) => { + const { dispatch } = useWorkoutsContext() + + const handleClick = async () => { + const response = await fetch('/api/workouts/' + workout._id, { + method: 'DELETE', + }) + const json = await response.json() + + if (response.ok) { + dispatch({ type: 'DELETE_WORKOUT', payload: json }) + } + } return (

{workout.title}

-

Load (kg): {workout.load}

-

Number of reps: {workout.reps}

-

{workout.createdAt}

+

+ Load (kg): + {workout.load} +

+

+ Number of reps: + {workout.reps} +

+

+ {formatDistanceToNow(new Date(workout.createdAt), { addSuffix: true })} +

+ + delete +
) } -export default WorkoutDetails \ No newline at end of file +export default WorkoutDetails diff --git a/frontend/src/components/WorkoutForm.js b/frontend/src/components/WorkoutForm.js index b90d7d9..c4653c4 100644 --- a/frontend/src/components/WorkoutForm.js +++ b/frontend/src/components/WorkoutForm.js @@ -8,57 +8,62 @@ const WorkoutForm = () => { const [load, setLoad] = useState('') const [reps, setReps] = useState('') const [error, setError] = useState(null) + const [emptyFields, setEmptyFields] = useState([]) const handleSubmit = async (e) => { e.preventDefault() - const workout = {title, load, reps} - + const workout = { title, load, reps } + const response = await fetch('/api/workouts', { method: 'POST', body: JSON.stringify(workout), headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }) const json = await response.json() if (!response.ok) { setError(json.error) + setEmptyFields(json.emptyFields) } if (response.ok) { + setEmptyFields([]) setError(null) setTitle('') setLoad('') setReps('') - dispatch({type: 'CREATE_WORKOUT', payload: json}) + dispatch({ type: 'CREATE_WORKOUT', payload: json }) } - } return ( -
+

Add a New Workout

- setTitle(e.target.value)} + setTitle(e.target.value)} value={title} + className={emptyFields.includes('title') ? 'error' : ''} /> - setLoad(e.target.value)} + setLoad(e.target.value)} value={load} + className={emptyFields.includes('load') ? 'error' : ''} /> - setReps(e.target.value)} - value={reps} + setReps(e.target.value)} + value={reps} + className={emptyFields.includes('reps') ? 'error' : ''} /> @@ -67,4 +72,4 @@ const WorkoutForm = () => { ) } -export default WorkoutForm \ No newline at end of file +export default WorkoutForm diff --git a/frontend/src/context/WorkoutsContext.js b/frontend/src/context/WorkoutsContext.js index c9e0597..a66ec44 100644 --- a/frontend/src/context/WorkoutsContext.js +++ b/frontend/src/context/WorkoutsContext.js @@ -1,16 +1,22 @@ import { createContext, useReducer } from 'react' + + export const WorkoutsContext = createContext() export const workoutsReducer = (state, action) => { switch (action.type) { case 'SET_WORKOUTS': - return { - workouts: action.payload + return { + workouts: action.payload, } case 'CREATE_WORKOUT': - return { - workouts: [action.payload, ...state.workouts] + return { + workouts: [action.payload, ...state.workouts], + } + case 'DELETE_WORKOUT': + return { + workouts: state.workouts.filter((w) => w._id !== action.payload._id), } default: return state @@ -18,13 +24,13 @@ export const workoutsReducer = (state, action) => { } export const WorkoutsContextProvider = ({ children }) => { - const [state, dispatch] = useReducer(workoutsReducer, { - workouts: null + const [state, dispatch] = useReducer(workoutsReducer, { + workouts: null, }) - + return ( - { children } + {children} ) -} \ No newline at end of file +} diff --git a/frontend/src/index.css b/frontend/src/index.css index fc42497..88693a9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -96,4 +96,7 @@ div.error { color: var(--error); border-radius: 4px; margin: 20px 0; -} \ No newline at end of file +} +input.error { + border: 1px solid var(--error); +}