Revert "Adds the ability for users to create, update, and delete their own workouts"
This commit is contained in:
parent
dd0a143bef
commit
0de82a5858
|
@ -13,88 +13,13 @@ import DropdownButton from 'react-bootstrap/DropdownButton';
|
|||
import Modal from 'react-bootstrap/Modal';
|
||||
|
||||
// Firebase
|
||||
import { deleteDoc, doc, updateDoc } from 'firebase/firestore';
|
||||
import { deleteDoc, doc } from 'firebase/firestore';
|
||||
import { db } from '../../firebase-config';
|
||||
|
||||
// Custom components
|
||||
import CustomToast from './CustomToast';
|
||||
|
||||
// Authentication
|
||||
import { useAuth } from '../../context/authUserContext';
|
||||
|
||||
// Makes either a button, or a dropdown button
|
||||
function AButton({ type, name, id, handleModalOpen }) {
|
||||
if (type === 'user') {
|
||||
return (
|
||||
<DropdownButton title="...">
|
||||
<Link href={`/userworkouts/edit/${id}`} passHref>
|
||||
<Dropdown.Item>Edit {name}</Dropdown.Item>
|
||||
</Link>
|
||||
<Dropdown.Item onClick={handleModalOpen}>Delete {name}</Dropdown.Item>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'exercise') {
|
||||
return (
|
||||
<DropdownButton title="...">
|
||||
<Link href={`/exercises/edit/${id}`} passHref>
|
||||
<Dropdown.Item>Edit {name}</Dropdown.Item>
|
||||
</Link>
|
||||
<Dropdown.Item onClick={handleModalOpen}>Delete {name}</Dropdown.Item>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'workout') {
|
||||
return (
|
||||
<DropdownButton title="...">
|
||||
<Link href={`/workouts/edit/${id}`} passHref>
|
||||
<Dropdown.Item>Edit {name}</Dropdown.Item>
|
||||
</Link>
|
||||
<Dropdown.Item onClick={handleModalOpen}>Delete {name}</Dropdown.Item>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function DeleteModal({
|
||||
name,
|
||||
type,
|
||||
isModalOpen,
|
||||
handleModalClose,
|
||||
handleDelete,
|
||||
}) {
|
||||
return (
|
||||
<Modal show={isModalOpen} onHide={handleModalClose} centered size="lg">
|
||||
<Modal.Header>
|
||||
<Modal.Title>
|
||||
{type === 'user' ? (
|
||||
<p>Delete your workout '{name}'?</p>
|
||||
) : (
|
||||
<p>
|
||||
Delete {type} '{name}'?
|
||||
</p>
|
||||
)}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleModalClose}>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button variant="danger" onClick={handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EditButton({ type, id, name, onDelete }) {
|
||||
const { authUser } = useAuth();
|
||||
|
||||
/* Handles state for the delete toast */
|
||||
const [isToastActive, setToastActive] = useState({});
|
||||
const handleToastOpen = ({ title, body, error }) => {
|
||||
|
@ -112,16 +37,6 @@ export default function EditButton({ type, id, name, onDelete }) {
|
|||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (type === 'user') {
|
||||
const filtered = authUser.createdWorkouts.filter(
|
||||
(workout) => workout.id !== id
|
||||
);
|
||||
authUser.createdWorkouts = filtered;
|
||||
updateDoc(doc(db, 'users', authUser.uid), {
|
||||
createdWorkouts: filtered,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'exercise') {
|
||||
deleteDoc(doc(db, 'exercises', id))
|
||||
.then(() => {
|
||||
|
@ -136,9 +51,7 @@ export default function EditButton({ type, id, name, onDelete }) {
|
|||
error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'workout') {
|
||||
} else {
|
||||
deleteDoc(doc(db, 'workouts', id))
|
||||
.then(() => {
|
||||
handleToastOpen({
|
||||
|
@ -157,6 +70,51 @@ export default function EditButton({ type, id, name, onDelete }) {
|
|||
handleModalClose();
|
||||
};
|
||||
|
||||
// Makes either a button, or a dropdown button
|
||||
const makeButton = (title) => (
|
||||
<DropdownButton title="...">
|
||||
{title === 'exercise' ? (
|
||||
<Link href={`/exercises/edit/${id}`} passHref>
|
||||
<Dropdown.Item>Edit {title}</Dropdown.Item>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={`/workouts/edit/${id}`} passHref>
|
||||
<Dropdown.Item>Edit {title}</Dropdown.Item>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Dropdown.Item onClick={handleModalOpen}>Delete {title}</Dropdown.Item>
|
||||
</DropdownButton>
|
||||
);
|
||||
|
||||
// Creates the modal only if there is an id.
|
||||
const makeModal = (toShow) => {
|
||||
if (toShow !== undefined) {
|
||||
return (
|
||||
<Modal show={isModalOpen} onHide={handleModalClose} centered size="lg">
|
||||
<Modal.Header>
|
||||
<Modal.Title>
|
||||
<p>
|
||||
Delete {type} '{name}'?
|
||||
</p>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleModalClose}>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button variant="danger" onClick={handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Creates the toast for deleting
|
||||
function makeToast({ title, body, error }) {
|
||||
if (title !== undefined) {
|
||||
|
@ -171,23 +129,11 @@ export default function EditButton({ type, id, name, onDelete }) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AButton
|
||||
type={type}
|
||||
name={name}
|
||||
id={id}
|
||||
handleModalOpen={handleModalOpen}
|
||||
/>
|
||||
{id !== undefined && (
|
||||
<DeleteModal
|
||||
name={name}
|
||||
type={type}
|
||||
isModalOpen={isModalOpen}
|
||||
handleModalClose={handleModalClose}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
)}
|
||||
{makeButton(type)}
|
||||
{makeModal(id)}
|
||||
{makeToast(isToastActive)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -153,30 +153,14 @@ export default function Element({ element, type, onDelete, testAuth }) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'workouts') {
|
||||
return (
|
||||
<EditButton
|
||||
type="workout"
|
||||
id={element.id}
|
||||
name={element.name}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type.includes('user')) {
|
||||
return (
|
||||
<EditButton
|
||||
type="user"
|
||||
id={element.id}
|
||||
name={element.name}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return (
|
||||
<EditButton
|
||||
type="workout"
|
||||
id={element.id}
|
||||
name={element.name}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const makeMuscles = () => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-nested-ternary */
|
||||
// React
|
||||
import React, { useState } from 'react';
|
||||
|
||||
|
@ -17,7 +18,7 @@ import SelectedElement from './SelectedElement';
|
|||
* @param {*} listType Either "radio" or "checkbox".
|
||||
* @param {*} selected State of which elements are selected. if checkbox, must be an array.
|
||||
* @param {*} setSelected The function that sets the state of selected
|
||||
* @param {*} type Either "exercises", "workouts", or "user" (for user workouts).
|
||||
* @param {*} type Either "exercises" or "workouts"
|
||||
* @param {*} onDelete The callback function to handle an element being deleted from the list.
|
||||
* @returns
|
||||
*/
|
||||
|
@ -64,24 +65,6 @@ export default function List({
|
|||
});
|
||||
});
|
||||
|
||||
const placeholderText = () => {
|
||||
if (filteredList.length === 0) {
|
||||
if (type === 'user') {
|
||||
return <h3>No user workouts available.</h3>;
|
||||
}
|
||||
|
||||
if (type === 'workouts') {
|
||||
return <h3>No workouts available.</h3>;
|
||||
}
|
||||
|
||||
if (type === 'exercises') {
|
||||
return <h3>No exercises available.</h3>;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SearchFilterBar
|
||||
|
@ -97,8 +80,11 @@ export default function List({
|
|||
vertical
|
||||
name="button-list"
|
||||
>
|
||||
{placeholderText}
|
||||
{filteredList.length !== 0 &&
|
||||
{filteredList.length === 0 && type !== 'edit' ? (
|
||||
<h3>No {type} available</h3>
|
||||
) : filteredList.length === 0 && type !== 'edit' ? (
|
||||
<h3>No exercise available</h3>
|
||||
) : (
|
||||
filteredList.map((element) => (
|
||||
<ToggleButton
|
||||
className={styles.list}
|
||||
|
@ -107,7 +93,7 @@ export default function List({
|
|||
name={listType}
|
||||
value={element}
|
||||
>
|
||||
{selected && selected.name === element.name ? (
|
||||
{selected.name === element.name ? (
|
||||
<SelectedElement
|
||||
element={element}
|
||||
type={type}
|
||||
|
@ -117,18 +103,10 @@ export default function List({
|
|||
<Element element={element} type={type} onDelete={onDelete} />
|
||||
)}
|
||||
</ToggleButton>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// {selected && selected.name === element.name} ? (
|
||||
// <SelectedElement
|
||||
// element={element}
|
||||
// type={type}
|
||||
// onDelete={onDelete}
|
||||
// />
|
||||
// ) : (
|
||||
// <Element element={element} type={type} onDelete={onDelete} />)
|
||||
|
|
|
@ -147,30 +147,14 @@ export default function Element({ element, type, onDelete }) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'workouts') {
|
||||
return (
|
||||
<EditButton
|
||||
type="workout"
|
||||
id={element.id}
|
||||
name={element.name}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type.includes('user')) {
|
||||
return (
|
||||
<EditButton
|
||||
type="user"
|
||||
id={element.id}
|
||||
name={element.name}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return (
|
||||
<EditButton
|
||||
type="workout"
|
||||
id={element.id}
|
||||
name={element.name}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const makeMuscles = () => {
|
||||
|
|
|
@ -20,41 +20,9 @@ import styles from '../../styles/Navbar.module.css';
|
|||
// User Authentication
|
||||
import { useAuth } from '../../context/authUserContext';
|
||||
|
||||
function NewLink() {
|
||||
export default function TopNavbar() {
|
||||
const router = useRouter();
|
||||
|
||||
const { authUser } = useAuth();
|
||||
|
||||
if (authUser) {
|
||||
if (authUser.role === 0 && router.pathname.includes('exercises')) {
|
||||
return (
|
||||
<Link href="/exercises/create" passHref>
|
||||
<Nav.Link className={styles.item}>New exercise</Nav.Link>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (router.pathname.includes('userworkouts')) {
|
||||
return (
|
||||
<Link href="/userworkouts/create" passHref>
|
||||
<Nav.Link className={styles.item}>New user workout</Nav.Link>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (authUser.role === 0 && router.pathname.includes('workouts')) {
|
||||
return (
|
||||
<Link href="/workouts/create" passHref>
|
||||
<Nav.Link className={styles.item}>New workout</Nav.Link>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function TopNavbar() {
|
||||
// State to keep track of whether to show sign in view
|
||||
const [show, setShow] = useState(false);
|
||||
const handleShow = () => setShow(true);
|
||||
|
@ -87,7 +55,16 @@ export default function TopNavbar() {
|
|||
</Nav>
|
||||
|
||||
<Nav>
|
||||
<NewLink />
|
||||
{authUser &&
|
||||
(router.pathname.includes('exercises') ? (
|
||||
<Link href="/exercises/create" passHref>
|
||||
<Nav.Link className={styles.item}>New exercise</Nav.Link>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href="/workouts/create" passHref>
|
||||
<Nav.Link className={styles.item}>New workout</Nav.Link>
|
||||
</Link>
|
||||
))}
|
||||
<Nav.Link className={styles.item}>
|
||||
{authUser === null ? (
|
||||
<Nav onClick={handleShow}>Sign In</Nav>
|
||||
|
|
|
@ -21,7 +21,6 @@ const formatAuthUser = (user, id) => ({
|
|||
role: user.role,
|
||||
favouriteWorkouts: user.favouriteWorkouts,
|
||||
favouriteExercises: user.favouriteExercises,
|
||||
createdWorkouts: user.createdWorkouts,
|
||||
});
|
||||
|
||||
const createNewUser = async (user) => {
|
||||
|
@ -32,7 +31,6 @@ const createNewUser = async (user) => {
|
|||
role: 1,
|
||||
favouriteWorkouts: [],
|
||||
favouriteExercises: [],
|
||||
createdWorkouts: [],
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
export default function handler(req, res) {
|
||||
const { body } = req;
|
||||
|
||||
if (!body.muscleGroups) {
|
||||
body.muscleGroups = [];
|
||||
}
|
||||
|
||||
/* Cannot have workouts without a name or exercises */
|
||||
if (!body.name) {
|
||||
return res.status(400).json({ error: 'Workout name required.' });
|
||||
}
|
||||
|
||||
if (body.exercises.length < 1) {
|
||||
return res.status(400).json({ error: 'Workout contains no exercises.' });
|
||||
}
|
||||
|
||||
return res.status(200).json({ data: req.body });
|
||||
}
|
|
@ -1,364 +0,0 @@
|
|||
// React
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
// Next components
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
// Bootstrap components
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
|
||||
// Firebase
|
||||
import {
|
||||
collection,
|
||||
query,
|
||||
orderBy,
|
||||
getDocs,
|
||||
doc,
|
||||
updateDoc,
|
||||
} from 'firebase/firestore';
|
||||
import { db } from '../../../firebase-config';
|
||||
|
||||
// Custom components
|
||||
import AddExerciseModal from '../../../components/WorkoutForms/AddExerciseModal';
|
||||
import CustomAlert from '../../../components/EditButton/CustomAlert';
|
||||
import EditExerciseModal from '../../../components/WorkoutForms/EditExerciseModal';
|
||||
import ExerciseElement from '../../../components/WorkoutForms/ExerciseElement';
|
||||
import TopNavbar from '../../../components/Navbar/Navbar';
|
||||
|
||||
// Styles
|
||||
import styles from '../../../styles/WorkoutForms/WorkoutForm.module.css';
|
||||
|
||||
// Authentication
|
||||
import { useAuth } from '../../../context/authUserContext';
|
||||
|
||||
// Get reference to exercises and workouts collections
|
||||
const exercisesCollectionRef = collection(db, 'exercises');
|
||||
|
||||
function WorkoutForm() {
|
||||
const router = useRouter();
|
||||
const { authUser } = useAuth();
|
||||
|
||||
/* Ensures that the database is only queried once for data */
|
||||
const isFirstLoad = useRef(false);
|
||||
|
||||
/* Handles state for validation of form */
|
||||
// TODO: Form validation
|
||||
// const [validated, setValidated] = useState(false);
|
||||
|
||||
/* Handles state for the alert */
|
||||
const [isAlertActive, setAlertActive] = useState({});
|
||||
const handleAlertOpen = ({ heading, body, variant }) => {
|
||||
setAlertActive({ heading, body, variant });
|
||||
};
|
||||
const handleAlertClose = () => {
|
||||
setAlertActive({});
|
||||
};
|
||||
|
||||
/* Used to store the ids of exercises that have been added to the workout. */
|
||||
const [exerciseGroups, setExerciseGroups] = useState([]);
|
||||
|
||||
/* Used to store a list of the exercises that can be included in the workout */
|
||||
const [exercises, setExercises] = useState(undefined);
|
||||
|
||||
/* Used to store the selected exercise in the 'Add new exercise' modal */
|
||||
const [selectedExercise, setSelectedExercise] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
const getExercises = async () => {
|
||||
const q = query(exercisesCollectionRef, orderBy('name'));
|
||||
const data = await getDocs(q);
|
||||
setExercises(data.docs.map((d) => ({ ...d.data(), id: d.id })));
|
||||
};
|
||||
|
||||
if (!isFirstLoad.current) {
|
||||
getExercises();
|
||||
isFirstLoad.current = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
/* Keeps track of which index the exercise is in the workout */
|
||||
const index = useRef(0);
|
||||
|
||||
const updateExercises = (ex) => {
|
||||
setExerciseGroups(exerciseGroups.concat(ex));
|
||||
setSelectedExercise({});
|
||||
};
|
||||
|
||||
const deleteExercise = (toDelete) => {
|
||||
const newGroups = exerciseGroups.filter((ex) => ex.index !== toDelete);
|
||||
for (let i = 0; i < exerciseGroups.length - 1; i += 1) {
|
||||
newGroups[i].index = i;
|
||||
}
|
||||
setExerciseGroups(newGroups);
|
||||
index.current -= 1;
|
||||
};
|
||||
|
||||
/* Handles state for the add exercise modal */
|
||||
const [isAddExerciseModalOpen, setAddExerciseModalOpen] = useState(false);
|
||||
const handleAddExerciseModalOpen = () => setAddExerciseModalOpen(true);
|
||||
const handleAddExerciseModalClose = () => {
|
||||
/* This check is to differentiate between cancelling or actually adding an exercise */
|
||||
if (selectedExercise.id) {
|
||||
updateExercises({
|
||||
...selectedExercise,
|
||||
index: index.current,
|
||||
sets: 0,
|
||||
reps: 0,
|
||||
});
|
||||
index.current += 1;
|
||||
}
|
||||
setAddExerciseModalOpen(false);
|
||||
};
|
||||
|
||||
/* Handles state for the editing sets/reps for an exercise modal */
|
||||
const [isEditExerciseModalOpen, setEditExerciseModalOpen] = useState(false);
|
||||
const handleEditExerciseModalOpen = (ex) => {
|
||||
setSelectedExercise(ex);
|
||||
setEditExerciseModalOpen(true);
|
||||
};
|
||||
const handleEditExerciseModalClose = () => {
|
||||
for (let i = 0; i < exerciseGroups.length; i += 1) {
|
||||
if (exerciseGroups[i].id === selectedExercise.id) {
|
||||
exerciseGroups[i].sets = selectedExercise.sets;
|
||||
exerciseGroups[i].reps = selectedExercise.reps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setExerciseGroups(exerciseGroups);
|
||||
setEditExerciseModalOpen(false);
|
||||
};
|
||||
|
||||
/* Transforms an exercise into the correct data structure for the form submission */
|
||||
const getRepsSetsMuscles = () => {
|
||||
const exercisesList = [];
|
||||
const muscleGroupsList = [];
|
||||
|
||||
for (let i = 0; i < exerciseGroups.length; i += 1) {
|
||||
/* Not sure why, but I can't seem to access the data directly here.
|
||||
* So this is a workaround.
|
||||
*/
|
||||
const obj = { ...exerciseGroups[i] };
|
||||
delete obj.instructions;
|
||||
delete obj.equipment;
|
||||
delete obj.videoURL;
|
||||
delete obj.index;
|
||||
delete obj.muscleGroups;
|
||||
|
||||
/* Get the muscle groups from the exercises */
|
||||
if (exercises) {
|
||||
for (let j = 0; j < exercises.length; j += 1) {
|
||||
if (exercises[j].id === obj.id) {
|
||||
for (let k = 0; k < exercises[j].muscleGroups.length; k += 1) {
|
||||
if (!muscleGroupsList.includes(exercises[j].muscleGroups[k])) {
|
||||
muscleGroupsList.push(exercises[j].muscleGroups[k]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exercisesList.push(obj);
|
||||
}
|
||||
|
||||
return [exercisesList, muscleGroupsList.sort()];
|
||||
};
|
||||
|
||||
/* Handles the submission of forms. */
|
||||
const handleSubmit = async (event) => {
|
||||
/* Prevent automatic submission and refreshing of the page. */
|
||||
event.preventDefault();
|
||||
|
||||
const [exercisesList, muscleGroups] = getRepsSetsMuscles(event.target);
|
||||
|
||||
const data = {
|
||||
name: event.target.workoutName.value,
|
||||
imgSrc: event.target.workoutImgSrc.value,
|
||||
imgAlt: event.target.workoutImgAlt.value,
|
||||
muscleGroups,
|
||||
exercises: exercisesList,
|
||||
id: `${authUser.uid}-${event.target.workoutName.value}`,
|
||||
};
|
||||
|
||||
/* Send the form data to the API and get a response */
|
||||
const response = await fetch('/api/userworkout', {
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
/* Get the response, add the document, create an alert, then redirect. */
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
handleAlertOpen({
|
||||
heading: 'Error',
|
||||
body: result.error,
|
||||
variant: 'danger',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (authUser) {
|
||||
if (authUser.createdWorkouts === undefined) {
|
||||
authUser.createdWorkouts = [result.data];
|
||||
} else {
|
||||
authUser.createdWorkouts.push(result.data);
|
||||
}
|
||||
updateDoc(doc(db, 'users', authUser.uid), {
|
||||
createdWorkouts: authUser.createdWorkouts,
|
||||
})
|
||||
.then(() => {
|
||||
handleAlertOpen({
|
||||
heading: 'Success!',
|
||||
body: `${result.data.name} was added to your workout list. Redirecting...`,
|
||||
variant: 'success',
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.push('/userworkouts');
|
||||
}, 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
handleAlertOpen({
|
||||
heading: 'Error',
|
||||
body: `${error.name}: ${error.code}`,
|
||||
variant: 'danger',
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const displayAlert = ({ heading, body, variant }) => {
|
||||
// TODO: Check if dismissible on error
|
||||
if (heading && body && variant) {
|
||||
return (
|
||||
<CustomAlert
|
||||
heading={heading}
|
||||
body={body}
|
||||
variant={variant}
|
||||
onClose={handleAlertClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/* Handles the submission of forms to edit sets/reps of an exercise */
|
||||
const handleEdit = (event) => {
|
||||
event.preventDefault();
|
||||
selectedExercise.sets = event.target.newsets.value;
|
||||
selectedExercise.reps = event.target.newreps.value;
|
||||
handleEditExerciseModalClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.form}>
|
||||
<div>
|
||||
<h2>Creating new user workout</h2>
|
||||
</div>
|
||||
|
||||
<Form onSubmit={handleSubmit} action="/api/userworkout" method="post">
|
||||
<div className="mt-3 mb-3">
|
||||
<Form.Label>Enter workout name:</Form.Label>
|
||||
<Form.Control
|
||||
id="workoutName"
|
||||
type="text"
|
||||
placeholder="Enter workout name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Enter image URL to display</Form.Label>
|
||||
<Form.Control
|
||||
id="workoutImgSrc"
|
||||
type="url"
|
||||
placeholder="Enter image URL"
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Enter image alt</Form.Label>
|
||||
<Form.Control
|
||||
id="workoutImgAlt"
|
||||
type="text"
|
||||
placeholder="Enter image alt (in case the image doesn't load)"
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<Form.Label>Exercises in this workout:</Form.Label>
|
||||
<div className={styles.formexercises}>
|
||||
{exerciseGroups.length === 0 ? (
|
||||
<p>None</p>
|
||||
) : (
|
||||
exerciseGroups.map((ex) => (
|
||||
<ExerciseElement
|
||||
exercise={ex}
|
||||
key={ex.id}
|
||||
onClick={() => {
|
||||
handleEditExerciseModalOpen(ex);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Form.Group>
|
||||
|
||||
<div className={styles.addexercise}>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={handleAddExerciseModalOpen}
|
||||
className="mt-3 mb-3"
|
||||
>
|
||||
+ Add an exercise
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{displayAlert(isAlertActive)}
|
||||
|
||||
<div className={`mt-3 ${styles.buttongroup}`}>
|
||||
<Link href="/userworkouts" passHref>
|
||||
<Button variant="secondary" size="lg">
|
||||
Cancel
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="primary" type="submit" size="lg">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<AddExerciseModal
|
||||
show={isAddExerciseModalOpen}
|
||||
onClose={handleAddExerciseModalClose}
|
||||
list={exercises}
|
||||
setSelectedExercise={setSelectedExercise}
|
||||
/>
|
||||
|
||||
<EditExerciseModal
|
||||
show={isEditExerciseModalOpen}
|
||||
onClose={handleEditExerciseModalClose}
|
||||
exercise={selectedExercise}
|
||||
onSubmit={handleEdit}
|
||||
onDelete={() => {
|
||||
deleteExercise(selectedExercise.index);
|
||||
setEditExerciseModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreateWorkout() {
|
||||
return (
|
||||
<>
|
||||
<TopNavbar />
|
||||
<div className={styles.main}>
|
||||
<WorkoutForm />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,396 +0,0 @@
|
|||
// React
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
// Next components
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
// Bootstrap components
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
|
||||
// Firebase
|
||||
import {
|
||||
collection,
|
||||
updateDoc,
|
||||
query,
|
||||
orderBy,
|
||||
getDocs,
|
||||
doc,
|
||||
} from 'firebase/firestore';
|
||||
import { db } from '../../../firebase-config';
|
||||
|
||||
// Custom components
|
||||
import AddExerciseModal from '../../../components/WorkoutForms/AddExerciseModal';
|
||||
import CustomAlert from '../../../components/EditButton/CustomAlert';
|
||||
import EditExerciseModal from '../../../components/WorkoutForms/EditExerciseModal';
|
||||
import ExerciseElement from '../../../components/WorkoutForms/ExerciseElement';
|
||||
import TopNavbar from '../../../components/Navbar/Navbar';
|
||||
|
||||
// Styles
|
||||
import styles from '../../../styles/WorkoutForms/WorkoutForm.module.css';
|
||||
|
||||
// Authentication
|
||||
import { useAuth } from '../../../context/authUserContext';
|
||||
|
||||
// Get reference to exercises and workouts collections
|
||||
const exercisesCollectionRef = collection(db, 'exercises');
|
||||
|
||||
function WorkoutForm() {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
const { authUser } = useAuth();
|
||||
|
||||
/* Ensures that the database is only queried once for data */
|
||||
const isFirstLoad = useRef(false);
|
||||
|
||||
/* Handles state for validation of form */
|
||||
// TODO: Form validation
|
||||
// const [validated, setValidated] = useState(false);
|
||||
|
||||
/* Handles state for the alert */
|
||||
const [isAlertActive, setAlertActive] = useState({});
|
||||
const handleAlertOpen = ({ heading, body, variant }) => {
|
||||
setAlertActive({ heading, body, variant });
|
||||
};
|
||||
const handleAlertClose = () => {
|
||||
setAlertActive({});
|
||||
};
|
||||
|
||||
/* Used to store the ids of exercises that have been added to the workout. */
|
||||
const [exerciseGroups, setExerciseGroups] = useState([]);
|
||||
|
||||
/* Used to store a list of the exercises that can be included in the workout */
|
||||
const [exercises, setExercises] = useState(undefined);
|
||||
|
||||
/* Stores the current workout being edited. */
|
||||
const [workout, setWorkout] = useState({});
|
||||
|
||||
/* Used to store the selected exercise in the 'Add new exercise' modal */
|
||||
const [selectedExercise, setSelectedExercise] = useState({});
|
||||
|
||||
/* Keeps track of which index the exercise is in the workout */
|
||||
const index = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const getExercises = async () => {
|
||||
const q = query(exercisesCollectionRef, orderBy('name'));
|
||||
const data = await getDocs(q);
|
||||
setExercises(data.docs.map((d) => ({ ...d.data(), id: d.id })));
|
||||
};
|
||||
|
||||
const getWorkout = () => {
|
||||
if (authUser) {
|
||||
for (let i = 0; i < authUser.createdWorkouts.length; i += 1) {
|
||||
if (authUser.createdWorkouts[i].id === id) {
|
||||
setWorkout(authUser.createdWorkouts[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadExerciseGroups = () => {
|
||||
const newGroups = [];
|
||||
if (workout.exercises) {
|
||||
for (let i = 0; i < workout.exercises.length; i += 1) {
|
||||
newGroups.push({ ...workout.exercises[i], index: i });
|
||||
}
|
||||
index.current = workout.exercises.length;
|
||||
}
|
||||
setExerciseGroups(newGroups);
|
||||
};
|
||||
|
||||
if (!isFirstLoad.current) {
|
||||
getExercises();
|
||||
isFirstLoad.current = true;
|
||||
}
|
||||
|
||||
if (isFirstLoad.current) {
|
||||
getWorkout();
|
||||
loadExerciseGroups();
|
||||
}
|
||||
}, [authUser, id, workout.exercises]);
|
||||
|
||||
const updateExercises = (ex) => {
|
||||
setExerciseGroups(exerciseGroups.concat(ex));
|
||||
setSelectedExercise({});
|
||||
};
|
||||
|
||||
const deleteExercise = (toDelete) => {
|
||||
const newGroups = exerciseGroups.filter((ex) => ex.index !== toDelete);
|
||||
for (let i = 0; i < exerciseGroups.length - 1; i += 1) {
|
||||
newGroups[i].index = i;
|
||||
}
|
||||
setExerciseGroups(newGroups);
|
||||
index.current -= 1;
|
||||
};
|
||||
|
||||
/* Handles state for the add exercise modal */
|
||||
const [isAddExerciseModalOpen, setAddExerciseModalOpen] = useState(false);
|
||||
const handleAddExerciseModalOpen = () => setAddExerciseModalOpen(true);
|
||||
const handleAddExerciseModalClose = () => {
|
||||
/* This check is to differentiate between cancelling or actually adding an exercise */
|
||||
if (selectedExercise.id) {
|
||||
updateExercises({
|
||||
...selectedExercise,
|
||||
index: index.current,
|
||||
sets: 0,
|
||||
reps: 0,
|
||||
});
|
||||
index.current += 1;
|
||||
}
|
||||
setAddExerciseModalOpen(false);
|
||||
};
|
||||
|
||||
/* Handles state for the editing sets/reps for an exercise modal */
|
||||
const [isEditExerciseModalOpen, setEditExerciseModalOpen] = useState(false);
|
||||
const handleEditExerciseModalOpen = (ex) => {
|
||||
setSelectedExercise(ex);
|
||||
setEditExerciseModalOpen(true);
|
||||
};
|
||||
const handleEditExerciseModalClose = () => {
|
||||
for (let i = 0; i < exerciseGroups.length; i += 1) {
|
||||
if (exerciseGroups[i].id === selectedExercise.id) {
|
||||
exerciseGroups[i].sets = selectedExercise.sets;
|
||||
exerciseGroups[i].reps = selectedExercise.reps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setExerciseGroups(exerciseGroups);
|
||||
setEditExerciseModalOpen(false);
|
||||
};
|
||||
|
||||
/* Transforms an exercise into the correct data structure for the form submission */
|
||||
const getRepsSetsMuscles = () => {
|
||||
const exercisesList = [];
|
||||
const muscleGroupsList = [];
|
||||
|
||||
for (let i = 0; i < exerciseGroups.length; i += 1) {
|
||||
/* Not sure why, but I can't seem to access the data directly here.
|
||||
* So this is a workaround.
|
||||
*/
|
||||
const obj = { ...exerciseGroups[i] };
|
||||
delete obj.instructions;
|
||||
delete obj.equipment;
|
||||
delete obj.videoURL;
|
||||
delete obj.index;
|
||||
delete obj.muscleGroups;
|
||||
|
||||
/* Get the muscle groups from the exercises */
|
||||
if (exercises) {
|
||||
for (let j = 0; j < exercises.length; j += 1) {
|
||||
if (exercises[j].id === obj.id) {
|
||||
for (let k = 0; k < exercises[j].muscleGroups.length; k += 1) {
|
||||
if (!muscleGroupsList.includes(exercises[j].muscleGroups[k])) {
|
||||
muscleGroupsList.push(exercises[j].muscleGroups[k]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exercisesList.push(obj);
|
||||
}
|
||||
|
||||
return [exercisesList, muscleGroupsList.sort()];
|
||||
};
|
||||
|
||||
/* Handles the submission of forms. */
|
||||
const handleSubmit = async (event) => {
|
||||
/* Prevent automatic submission and refreshing of the page. */
|
||||
event.preventDefault();
|
||||
|
||||
const [exercisesList, muscleGroups] = getRepsSetsMuscles();
|
||||
|
||||
const data = {
|
||||
name: event.target.workoutName.value,
|
||||
imgSrc: event.target.workoutImgSrc.value,
|
||||
imgAlt: event.target.workoutImgAlt.value,
|
||||
muscleGroups,
|
||||
exercises: exercisesList,
|
||||
id: `${authUser.uid}-${event.target.workoutName.value}`,
|
||||
};
|
||||
|
||||
/* Send the form data to the API and get a response */
|
||||
const response = await fetch('/api/userworkout', {
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
/* Get the response, add the document, create an alert, then redirect. */
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
handleAlertOpen({
|
||||
heading: 'Error',
|
||||
body: result.error,
|
||||
variant: 'danger',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < authUser.createdWorkouts.length; i += 1) {
|
||||
if (authUser.createdWorkouts[i].id === result.data.id) {
|
||||
authUser.createdWorkouts[i] = { ...result.data };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (authUser) {
|
||||
updateDoc(doc(db, 'users', authUser.uid), {
|
||||
createdWorkouts: authUser.createdWorkouts,
|
||||
})
|
||||
.then(() => {
|
||||
handleAlertOpen({
|
||||
heading: 'Success!',
|
||||
body: `${result.data.name} was updated in your workout list. Redirecting...`,
|
||||
variant: 'success',
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.push('/userworkouts');
|
||||
}, 3000);
|
||||
})
|
||||
.catch((error) => {
|
||||
handleAlertOpen({
|
||||
heading: 'Error',
|
||||
body: `${error.name}: ${error.code}`,
|
||||
variant: 'danger',
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const displayAlert = ({ heading, body, variant }) => {
|
||||
if (heading && body && variant) {
|
||||
return (
|
||||
<CustomAlert
|
||||
heading={heading}
|
||||
body={body}
|
||||
variant={variant}
|
||||
onClose={handleAlertClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/* Handles the submission of forms to edit sets/reps of an exercise */
|
||||
const handleEdit = (event) => {
|
||||
event.preventDefault();
|
||||
selectedExercise.sets = event.target.newsets.value;
|
||||
selectedExercise.reps = event.target.newreps.value;
|
||||
handleEditExerciseModalClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.form}>
|
||||
<div>
|
||||
<h2>Editing {workout.name}</h2>
|
||||
</div>
|
||||
|
||||
<Form onSubmit={handleSubmit} action="/apiwout" method="post">
|
||||
<div className="mt-3 mb-3">
|
||||
<Form.Label>Enter workout name:</Form.Label>
|
||||
<Form.Control
|
||||
id="workoutName"
|
||||
type="text"
|
||||
defaultValue={workout.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Enter image URL to display</Form.Label>
|
||||
<Form.Control
|
||||
id="workoutImgSrc"
|
||||
type="url"
|
||||
defaultValue={workout.imgSrc}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Enter image alt</Form.Label>
|
||||
<Form.Control
|
||||
id="workoutImgAlt"
|
||||
type="text"
|
||||
defaultValue={workout.imgAlt}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<Form.Label>Exercises in this workout:</Form.Label>
|
||||
<div className={styles.formexercises}>
|
||||
{exerciseGroups.length === 0 ? (
|
||||
<p>None</p>
|
||||
) : (
|
||||
exerciseGroups.map((ex) => (
|
||||
<ExerciseElement
|
||||
exercise={ex}
|
||||
key={ex.id}
|
||||
onClick={() => {
|
||||
handleEditExerciseModalOpen(ex);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Form.Group>
|
||||
|
||||
<div className={styles.addexercise}>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={handleAddExerciseModalOpen}
|
||||
className="mt-3 mb-3"
|
||||
>
|
||||
+ Add an exercise
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{displayAlert(isAlertActive)}
|
||||
|
||||
<div className={`mt-3 ${styles.buttongroup}`}>
|
||||
<Link href="/userworkouts" passHref>
|
||||
<Button variant="secondary" size="lg">
|
||||
Cancel
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="primary" type="submit" size="lg">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<AddExerciseModal
|
||||
show={isAddExerciseModalOpen}
|
||||
onClose={handleAddExerciseModalClose}
|
||||
list={exercises}
|
||||
setSelectedExercise={setSelectedExercise}
|
||||
/>
|
||||
|
||||
<EditExerciseModal
|
||||
show={isEditExerciseModalOpen}
|
||||
onClose={handleEditExerciseModalClose}
|
||||
exercise={selectedExercise}
|
||||
onSubmit={handleEdit}
|
||||
onDelete={() => {
|
||||
deleteExercise(selectedExercise.index);
|
||||
setEditExerciseModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreateWorkout() {
|
||||
return (
|
||||
<>
|
||||
<TopNavbar />
|
||||
<div className={styles.main}>
|
||||
<WorkoutForm />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
// React
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
// Bootstrap components
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
|
||||
// Next components
|
||||
import Image from 'next/image';
|
||||
|
||||
// Custom components
|
||||
import List from '../../components/List/List';
|
||||
import TopNavbar from '../../components/Navbar/Navbar';
|
||||
import WorkoutList from '../../components/WorkoutList/WorkoutList';
|
||||
|
||||
// Styles
|
||||
import styles from '../../styles/Workouts/Workouts.module.css';
|
||||
|
||||
// Authentication
|
||||
import { useAuth } from '../../context/authUserContext';
|
||||
|
||||
export default function WorkoutsPage({ testData }) {
|
||||
const [workoutList, setWorkoutList] = useState([]);
|
||||
const { authUser } = useAuth();
|
||||
|
||||
const [selectedWorkout, setSelectedWorkout] = useState();
|
||||
const [workouts, setWorkouts] = useState([]);
|
||||
|
||||
/* Only render Card if innerWidth > 576px (small breakpoint) */
|
||||
const [toRenderCard, setRenderCard] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (testData !== undefined) {
|
||||
setWorkouts(testData);
|
||||
}
|
||||
|
||||
if (authUser) {
|
||||
const createdWorkouts =
|
||||
authUser.createdWorkouts !== undefined ? authUser.createdWorkouts : [];
|
||||
setWorkouts(createdWorkouts);
|
||||
setSelectedWorkout(createdWorkouts[0]);
|
||||
setWorkoutList(createdWorkouts);
|
||||
}
|
||||
|
||||
if (window.innerWidth < 576) {
|
||||
setRenderCard(false);
|
||||
}
|
||||
}, [authUser, workouts, testData]);
|
||||
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
/* Handles the onClick events for each workout.
|
||||
* The split is needed to handle both mobile/desktop views at the same time.
|
||||
* If the window width is small, the modal is opened when the elements are
|
||||
* clicked. Otherwise, the card is updated.
|
||||
*/
|
||||
const onClick = (newWorkout) => {
|
||||
if (!toRenderCard) {
|
||||
handleOpen();
|
||||
}
|
||||
setSelectedWorkout(newWorkout);
|
||||
};
|
||||
|
||||
/* When an exercise is deleted, remove it from the list. */
|
||||
const onDelete = (id) => {
|
||||
setWorkoutList(workouts.filter((doc) => doc.id !== id));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopNavbar />
|
||||
|
||||
<Container className={styles.container}>
|
||||
<Row>
|
||||
{toRenderCard && (
|
||||
<Col xs={6}>
|
||||
{selectedWorkout != null && (
|
||||
<div className={styles.imgcontainer}>
|
||||
<Image
|
||||
src={selectedWorkout.imgSrc}
|
||||
alt={selectedWorkout.imgAlt}
|
||||
width="100%"
|
||||
height="70%"
|
||||
layout="responsive"
|
||||
object-fit="cover"
|
||||
style={{ width: '100%', height: '48vh' }}
|
||||
/>
|
||||
<div className={styles.textblock}>
|
||||
<h1>{selectedWorkout.name}</h1>
|
||||
<p>{selectedWorkout.muscleGroups.join(', ')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedWorkout != null && (
|
||||
<div>
|
||||
<main className={styles.workoutlist}>
|
||||
<WorkoutList exerciseList={selectedWorkout.exercises} />
|
||||
</main>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
)}
|
||||
|
||||
<Col>
|
||||
<List
|
||||
list={workoutList}
|
||||
listType="radio"
|
||||
selected={selectedWorkout}
|
||||
setSelected={onClick}
|
||||
type="user"
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
{selectedWorkout !== undefined && (
|
||||
<Modal show={isOpen} onHide={handleClose} centered scrollable size="lg">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{selectedWorkout.name}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body>
|
||||
{selectedWorkout != null && (
|
||||
<div>
|
||||
<main className={styles.workoutlist}>
|
||||
<WorkoutList exerciseList={selectedWorkout.exercises} />
|
||||
</main>
|
||||
</div>
|
||||
)}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="primary" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -284,7 +284,6 @@ function WorkoutForm() {
|
|||
exerciseGroups.map((ex) => (
|
||||
<ExerciseElement
|
||||
exercise={ex}
|
||||
key={ex.id}
|
||||
onClick={() => {
|
||||
handleEditExerciseModalOpen(ex);
|
||||
}}
|
||||
|
|
|
@ -314,7 +314,6 @@ function WorkoutForm() {
|
|||
exerciseGroups.map((ex) => (
|
||||
<ExerciseElement
|
||||
exercise={ex}
|
||||
key={ex.id}
|
||||
onClick={() => {
|
||||
handleEditExerciseModalOpen(ex);
|
||||
}}
|
||||
|
|
Loading…
Reference in New Issue