Split exercise forms, fix selecting elements

This commit is contained in:
Rory Healy 2022-10-23 02:38:43 +11:00
parent 20c030d4a0
commit 0f6be7635c
12 changed files with 417 additions and 237 deletions

View File

@ -0,0 +1,14 @@
// React
import React from 'react';
// Bootstrap components
import Alert from 'react-bootstrap/Alert';
export default function CustomAlert({ heading, body, variant, onClose }) {
return (
<Alert variant={variant} onClose={onClose} dismissible>
<Alert.Heading>{heading}</Alert.Heading>
<p>{body}</p>
</Alert>
);
}

View File

@ -5,7 +5,7 @@ import React from 'react';
import Toast from 'react-bootstrap/Toast';
// Styles
import styles from '../../styles/Crud.module.css';
import styles from '../../styles/EditButton.module.css';
export default function CustomToast({ title, body, error, onClose }) {
return (

View File

@ -17,7 +17,7 @@ import { db } from '../../firebase-config';
// Custom components
import CustomToast from './CustomToast';
export default function CRUDButton({ type, create, id, name }) {
export default function EditButton({ type, id, name, onDelete }) {
/* Handles state for the delete toast */
const [isToastActive, setToastActive] = useState({});
const handleToastOpen = ({ title, body, error }) => {
@ -35,7 +35,6 @@ export default function CRUDButton({ type, create, id, name }) {
};
const handleDelete = () => {
// TODO: Refresh exercise/workout list after deletion.
if (type === 'exercise') {
deleteDoc(doc(db, 'exercises', id))
.then(() => {
@ -65,44 +64,29 @@ export default function CRUDButton({ type, create, id, name }) {
});
});
}
onDelete(id);
handleModalClose();
};
// Makes either a button, or a dropdown button
function makeButton(urlBase, toCreate) {
// Get URL base
let url = '';
if (urlBase === 'exercise') {
url = '/exercises/edit';
} else if (urlBase === 'workout') {
url = '/workouts/edit';
}
// Only regular button for creating
if (toCreate !== undefined) {
return (
<Link href={{ pathname: url, query: 'type=create' }} passHref>
<Button variant="primary">New {urlBase}</Button>
const makeButton = (title) => (
<DropdownButton title="...">
{title === 'exercise' ? (
<Link href={`/exercises/edit/${id}`} passHref>
<Dropdown.Item>Edit {title}</Dropdown.Item>
</Link>
);
}
// Dropdown for editing and deleting
return (
<DropdownButton title="...">
<Link href={{ pathname: url, query: `type=edit&id=${id}` }} passHref>
<Dropdown.Item>Edit {urlBase}</Dropdown.Item>
) : (
<Link href={`/workouts/edit/${id}`} passHref>
<Dropdown.Item>Edit {title}</Dropdown.Item>
</Link>
<Dropdown.Item onClick={handleModalOpen}>
Delete {urlBase}
</Dropdown.Item>
</DropdownButton>
);
}
)}
<Dropdown.Item onClick={handleModalOpen}>Delete {title}</Dropdown.Item>
</DropdownButton>
);
// Creates the modal only if there is an id.
function makeModal(toShow) {
const makeModal = (toShow) => {
if (toShow !== undefined) {
return (
<Modal show={isModalOpen} onHide={handleModalClose} centered size="lg">
@ -127,7 +111,7 @@ export default function CRUDButton({ type, create, id, name }) {
);
}
return null;
}
};
// Creates the toast for deleting
function makeToast({ title, body, error }) {
@ -146,7 +130,7 @@ export default function CRUDButton({ type, create, id, name }) {
return (
<>
{makeButton(type, create)}
{makeButton(type)}
{makeModal(id)}
{makeToast(isToastActive)}
</>

View File

@ -9,7 +9,7 @@ import { collection, updateDoc, doc } from 'firebase/firestore';
import { db } from '../../firebase-config';
// Custom components
import CRUDButton from '../CRUDButton/CRUDButton';
import EditButton from '../EditButton/EditButton';
// Styles
import styles from '../../styles/Element.module.css';
@ -20,7 +20,7 @@ import { useAuth } from '../../context/authUserContext';
// Get reference to users collection
const usersCollectionRef = collection(db, 'users');
export default function Element({ element, type, onClick }) {
export default function Element({ element, type, onDelete }) {
/* Paths of the images of the favourite button */
const star = '/images/star.png';
const starFilled = '/images/starFilled.png';
@ -123,21 +123,27 @@ export default function Element({ element, type, onClick }) {
/* Crude check for checking if the element is an exercise or workout */
if (element.instructions !== undefined) {
return (
<CRUDButton type="exercise" id={element.id} name={element.name} />
<EditButton
type="exercise"
id={element.id}
name={element.name}
onDelete={onDelete}
/>
);
}
return <CRUDButton type="workout" id={element.id} name={element.name} />;
return (
<EditButton
type="workout"
id={element.id}
name={element.name}
onDelete={onDelete}
/>
);
}
return null;
};
return (
<div
className={styles.element}
onClick={onClick}
onKeyPress={onClick}
role="button"
tabIndex={0}
>
<div className={styles.element}>
<Image
src={element.imageSource}
alt={element.imageAlt}

View File

@ -17,6 +17,8 @@ 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" or "workouts"
* @param {*} onDelete The callback function to handle an element being deleted from the list.
* @returns
*/
export default function List({
@ -25,7 +27,7 @@ export default function List({
selected,
setSelected,
type,
onClick,
onDelete,
}) {
// A function to handle when a new element is selected
const handleChange = (e) => {
@ -62,12 +64,12 @@ export default function List({
id={`${listType}-${element.id}`}
variant="light"
name={listType}
value={element.id}
value={element}
>
{selected === element.name ? (
<SelectedElement element={element} type={type} />
) : (
<Element element={element} type={type} onClick={onClick} />
<Element element={element} type={type} onDelete={onDelete} />
)}
</ToggleButton>
))}

View File

@ -53,23 +53,11 @@ export default function TopNavbar() {
<Nav>
{authUser &&
(router.pathname.includes('exercises') ? (
<Link
href={{
pathname: '/exercises/edit',
query: 'type=create',
}}
passHref
>
<Link href="/exercises/create" passHref>
<Nav.Link className={styles.item}>New exercise</Nav.Link>
</Link>
) : (
<Link
href={{
pathname: '/workouts/edit',
query: 'type=create',
}}
passHref
>
<Link href="/workouts/create" passHref>
<Nav.Link className={styles.item}>New workout</Nav.Link>
</Link>
))}

View File

@ -12,6 +12,16 @@ const nextConfig = {
destination: '/exercises',
permanent: true,
},
{
source: '/exercises/edit',
destination: '/exercises',
permanent: true,
},
{
source: '/workouts/edit',
destination: '/workouts',
permanent: true,
},
]
},
};

View File

@ -0,0 +1,254 @@
// React
import React, { useEffect, useRef, useState } from 'react';
// Next components
import { useRouter } from 'next/router';
// Bootstrap components
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
// Firebase
import { addDoc, collection } from 'firebase/firestore';
import { db } from '../../../firebase-config';
// Custom components
import CustomAlert from '../../../components/EditButton/CustomAlert';
import TopNavbar from '../../../components/Navbar/Navbar';
// Styles
import styles from '../../../styles/EditButton.module.css';
// Get muscle list
import muscles from '../../../public/muscles.json' assert { type: 'json' };
// Get reference to workouts collection
const exercisesCollectionRef = collection(db, 'exercises');
// A form used for both creating and editing exercises
function ExerciseForm() {
/* Handles the state of the checkboxes */
const [checkboxes, setCheckboxes] = useState([]);
/* 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 manage the list of chosen muscle groups in the form */
const chosenMuscleGroups = useRef([]);
/* Used to prevent continuously creating the checkboxes */
const isFirstLoad = useRef(false);
/* Use Router for automatic redirect after successful form submission */
const router = useRouter();
useEffect(() => {
const updateChosenMuscles = (ex) => {
if (chosenMuscleGroups.current.includes(ex.target.value)) {
const filtered = chosenMuscleGroups.current.filter(
(i) => i !== ex.target.value
);
chosenMuscleGroups.current = filtered;
} else {
chosenMuscleGroups.current.push(ex.target.value);
}
};
const makeCheckboxes = () => {
const checkboxColumns = [];
for (let i = 0; i < muscles.length; i += 1) {
const { group, musclesList } = muscles[i];
const boxes = [];
for (let j = 0; j < musclesList.length; j += 1) {
const { mId, name } = musclesList[j];
boxes.push(
<div className="mb-3" key={mId}>
<Form.Check
type="checkbox"
id="exerciseMuscleGroups"
value={name}
label={name}
key={name}
onChange={updateChosenMuscles}
/>
</div>
);
}
/* TODO: This is still giving unique key errors, not sure why. */
checkboxColumns.push(
<Col key={group}>
<b>{group}</b>
{boxes}
</Col>
);
}
return checkboxColumns;
};
if (!isFirstLoad.current) {
setCheckboxes(makeCheckboxes);
isFirstLoad.current = true;
}
}, [isFirstLoad]);
/* Handles the submission of forms. */
const handleSubmit = async (event) => {
/* Prevent automatic submission and refreshing of the page. */
event.preventDefault();
/* TODO: Implement image uploading */
const data = {
name: event.target.exerciseName.value,
videoURL: event.target.exerciseURL.value,
instructions: event.target.exerciseInstructions.value,
equipment: event.target.exerciseEquipment.value,
imageSource: '/images/hammer-curls.png',
imageAlt: `Picture of ${event.target.exerciseName.value}`,
muscleGroups: chosenMuscleGroups.current,
};
/* Send the form data to the API and get a response */
const response = await fetch('/api/exercise', {
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();
addDoc(exercisesCollectionRef, result.data)
.then(() => {
handleAlertOpen({
heading: 'Success!',
body: `${result.data.name} was added to the exercise list. Redirecting...`,
variant: 'success',
});
setTimeout(() => {
router.push('/exercises');
}, 3000);
})
.catch((error) => {
handleAlertOpen({
heading: 'Error',
body: error,
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;
};
return (
<div className={styles.form}>
<h2>Creating new exercise</h2>
<Form onSubmit={handleSubmit} action="/api/exercise" method="post">
<Form.Group>
<Form.Label>Exercise name</Form.Label>
<Form.Control
id="exerciseName"
type="text"
placeholder="Enter exercise name"
/>
</Form.Group>
<Form.Group>
<Form.Label>Select targeted areas</Form.Label>
<Container fluid>
<Row>{checkboxes}</Row>
</Container>
</Form.Group>
<Form.Group>
<Form.Label>Enter video url to display</Form.Label>
<Form.Control
id="exerciseURL"
type="url"
placeholder="Enter video URL"
/>
</Form.Group>
{/* Not going to work just yet */}
{/* <Form.Group controlId="formThumbnail">
<Form.Label>Select a thumbnail</Form.Label>
<Form.Control type="file" />
</Form.Group>
<Form.Group controlId="formImageAlt">
<Form.Label>Enter text to show if image doesn&apos;t load</Form.Label>
{isEditingForm ? (
<Form.Control type="imageAlt" defaultValue={exercise.imageAlt} />
) : (
<Form.Control type="imageAlt" placeholder="Enter image alt" />
)}
</Form.Group> */}
<Form.Group>
<Form.Label>Equipment needed</Form.Label>
<Form.Control
id="exerciseEquipment"
type="text"
placeholder="Enter required equipment (leave blank for nothing)."
/>
</Form.Group>
<Form.Group>
<Form.Label>Exercise instructions</Form.Label>
<Form.Control
type="text"
id="exerciseInstructions"
as="textarea"
rows={5}
placeholder="Enter instructions to complete the exercise."
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
{displayAlert(isAlertActive)}
</div>
);
}
export default function CreateExercise() {
return (
<>
<TopNavbar />
{/* TODO: Preview of changes on side? */}
<div className={styles.main}>
<ExerciseForm />
</div>
</>
);
}

View File

@ -5,7 +5,6 @@ import React, { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/router';
// Bootstrap components
import Alert from 'react-bootstrap/Alert';
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';
import Col from 'react-bootstrap/Col';
@ -13,24 +12,24 @@ import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
// Firebase
import { getDoc, collection, addDoc, updateDoc, doc } from 'firebase/firestore';
import { getDoc, updateDoc, doc } from 'firebase/firestore';
import { db } from '../../../firebase-config';
// Custom components
import CustomAlert from '../../../components/EditButton/CustomAlert';
import TopNavbar from '../../../components/Navbar/Navbar';
// Styles
import styles from '../../../styles/Crud.module.css';
import styles from '../../../styles/EditButton.module.css';
// Get muscle list
import muscles from '../../../public/muscles.json' assert { type: 'json' };
// Get reference to workouts collection
const exercisesCollectionRef = collection(db, 'exercises');
// A form used for both creating and editing exercises
function ExerciseForm({ id }) {
const isEditingForm = id !== undefined;
function ExerciseForm() {
/* Get exercise ID from query parameters */
const router = useRouter();
const { id } = router.query;
/* Handles state for the exercise */
const [exercise, setExercise] = useState({});
@ -55,24 +54,15 @@ function ExerciseForm({ id }) {
const chosenMuscleGroups = useRef([]);
/* Used to ensure that the exercises collection is queried only once */
const [isExercisesReceived, setExercisesReceived] = useState(false);
/* Use Router for automatic redirect after successful form submission */
const router = useRouter();
const isFirstLoad = useRef(false);
useEffect(() => {
const getExercise = async () => {
if (isEditingForm) {
const exerciseDoc = await getDoc(doc(db, 'exercises', id));
chosenMuscleGroups.current = exerciseDoc.data().muscleGroups;
setExercise(exerciseDoc.data());
}
const exerciseDoc = await getDoc(doc(db, 'exercises', id));
chosenMuscleGroups.current = exerciseDoc.data().muscleGroups;
setExercise(exerciseDoc.data());
};
if (!isExercisesReceived) {
getExercise();
}
const updateChosenMuscles = (ex) => {
if (chosenMuscleGroups.current.includes(ex.target.value)) {
const filtered = chosenMuscleGroups.current.filter(
@ -86,7 +76,7 @@ function ExerciseForm({ id }) {
const makeCheckboxes = () => {
const checkboxColumns = [];
// Create checkboxes for all muscles, and pre-check boxes if editing
// Create checkboxes for all muscles, and pre-check boxes
const preChecked =
exercise.muscleGroups !== undefined ? exercise.muscleGroups : [];
for (let i = 0; i < muscles.length; i += 1) {
@ -94,9 +84,9 @@ function ExerciseForm({ id }) {
const boxes = [];
for (let j = 0; j < musclesList.length; j += 1) {
const { mId, name } = musclesList[j];
boxes.push(
<div className="mb-3" key={mId}>
{preChecked.includes(name) ? (
if (preChecked.includes(name)) {
boxes.push(
<div className="mb-3" key={mId}>
<Form.Check
type="checkbox"
id="exerciseMuscleGroups"
@ -106,7 +96,11 @@ function ExerciseForm({ id }) {
defaultChecked
onChange={updateChosenMuscles}
/>
) : (
</div>
);
} else {
boxes.push(
<div className="mb-3" key={mId}>
<Form.Check
type="checkbox"
id="exerciseMuscleGroups"
@ -115,9 +109,9 @@ function ExerciseForm({ id }) {
key={name}
onChange={updateChosenMuscles}
/>
)}
</div>
);
</div>
);
}
}
/* TODO: This is still giving unique key errors, not sure why. */
checkboxColumns.push(
@ -130,21 +124,19 @@ function ExerciseForm({ id }) {
return checkboxColumns;
};
if (isExercisesReceived) {
setCheckboxes(makeCheckboxes);
// TODO: Make the checkboxes pre-checked
if (!isFirstLoad.current) {
getExercise().then(setCheckboxes(makeCheckboxes()));
isFirstLoad.current = true;
}
return () => {
setExercisesReceived(true);
};
}, [id, exercise.muscleGroups, router, isEditingForm, isExercisesReceived]);
}, [exercise.muscleGroups, id]);
/* Handles the submission of forms. */
const handleSubmit = async (event) => {
/* Prevent automatic submission and refreshing of the page. */
event.preventDefault();
/* TODO: Implement muscleGroups and image uploading */
/* TODO: Implement image uploading */
const data = {
name: event.target.exerciseName.value,
videoURL: event.target.exerciseURL.value,
@ -164,56 +156,37 @@ function ExerciseForm({ id }) {
method: 'POST',
});
/* Get the response, update/add the document, create an alert, then redirect. */
/* Get the response, update the document, create an alert, then redirect. */
const result = await response.json();
if (isEditingForm) {
updateDoc(doc(db, 'exercises', id), result.data)
.then(() => {
handleAlertOpen({
heading: 'Success!',
body: `${result.data.name} was updated in the exercise list. Redirecting...`,
variant: 'success',
});
setTimeout(() => {
router.push('/exercises');
}, 3000);
})
.catch((error) => {
handleAlertOpen({
heading: 'Error',
body: error,
variant: 'danger',
});
updateDoc(doc(db, 'exercises', id), result.data)
.then(() => {
handleAlertOpen({
heading: 'Success!',
body: `${result.data.name} was updated in the exercise list. Redirecting...`,
variant: 'success',
});
} else {
addDoc(exercisesCollectionRef, result.data)
.then(() => {
handleAlertOpen({
heading: 'Success!',
body: `${result.data.name} was added to the exercise list. Redirecting...`,
variant: 'success',
});
setTimeout(() => {
router.push('/exercises');
}, 3000);
})
.catch((error) => {
handleAlertOpen({
heading: 'Error',
body: error,
variant: 'danger',
});
setTimeout(() => {
router.push('/exercises');
}, 3000);
})
.catch((error) => {
handleAlertOpen({
heading: 'Error',
body: error,
variant: 'danger',
});
}
});
};
const displayAlert = ({ heading, body, variant }) => {
if (heading !== undefined) {
if (heading && body && variant) {
return (
<Alert variant={variant} onClose={handleAlertClose} dismissible>
<Alert.Heading>{heading}</Alert.Heading>
<p>{body}</p>
</Alert>
<CustomAlert
heading={heading}
body={body}
variant={variant}
onClose={handleAlertClose}
/>
);
}
return null;
@ -221,28 +194,16 @@ function ExerciseForm({ id }) {
return (
<div className={styles.form}>
{displayAlert(isAlertActive)}
<h2>
{isEditingForm ? `Editing '${exercise.name}'` : 'Creating new exercise'}
</h2>
<h2>Editing {exercise.name}</h2>
<Form onSubmit={handleSubmit} action="/api/exercise" method="post">
<Form.Group>
<Form.Label>Exercise name</Form.Label>
{isEditingForm ? (
<Form.Control
id="exerciseName"
type="text"
defaultValue={exercise.name}
/>
) : (
<Form.Control
id="exerciseName"
type="text"
placeholder="Enter exercise name"
/>
)}
<Form.Control
id="exerciseName"
type="text"
defaultValue={exercise.name}
/>
</Form.Group>
<Form.Group>
@ -254,19 +215,11 @@ function ExerciseForm({ id }) {
<Form.Group>
<Form.Label>Enter video url to display</Form.Label>
{isEditingForm ? (
<Form.Control
id="exerciseURL"
type="url"
defaultValue={exercise.videoURL}
/>
) : (
<Form.Control
id="exerciseURL"
type="url"
placeholder="Enter video URL"
/>
)}
<Form.Control
id="exerciseURL"
type="url"
defaultValue={exercise.videoURL}
/>
</Form.Group>
{/* Not going to work just yet */}
@ -286,78 +239,42 @@ function ExerciseForm({ id }) {
<Form.Group>
<Form.Label>Equipment needed</Form.Label>
{isEditingForm ? (
<Form.Control
id="exerciseEquipment"
type="text"
defaultValue={exercise.equipment}
/>
) : (
<Form.Control
id="exerciseEquipment"
type="text"
placeholder="Enter required equipment"
/>
)}
<Form.Control
id="exerciseEquipment"
type="text"
defaultValue={exercise.equipment}
/>
</Form.Group>
<Form.Group>
<Form.Label>Exercise instructions</Form.Label>
{isEditingForm ? (
<Form.Control
type="text"
id="exerciseInstructions"
as="textarea"
rows={5}
defaultValue={exercise.instructions}
/>
) : (
<Form.Control
type="text"
id="exerciseInstructions"
as="textarea"
rows={5}
placeholder="Enter instructions to complete the exercise. Split into steps by entering a newline."
/>
)}
<Form.Control
type="text"
id="exerciseInstructions"
as="textarea"
rows={5}
defaultValue={exercise.instructions}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
{displayAlert(isAlertActive)}
</div>
);
}
// TODO: Ensure this page can only be accessed if signed in as admin
export default function EditExercise() {
// Get operation type and exercise ID from query parameters
const router = useRouter();
const { id } = router.query;
return (
<>
<TopNavbar />
{/* TODO: Preview of changes on side. */}
<div className={styles.main}>
{id !== undefined ? <ExerciseForm id={id} /> : <ExerciseForm />}
<ExerciseForm />
</div>
</>
);
}
export async function getServerSideProps({ query }) {
if (query.type !== 'create' && query.type !== 'edit') {
return {
redirect: {
permanent: false,
destination: '/exercises',
},
};
}
return {
props: {},
};
}

View File

@ -1,5 +1,5 @@
// React
import React, { useState, useEffect } from 'react';
import React, { useEffect, useState, useRef } from 'react';
// Bootstrap components
import Button from 'react-bootstrap/Button';
@ -39,7 +39,7 @@ export default function ExercisesPage() {
const [exercises, setExercises] = useState([]);
/* Used to ensure the database is only accessed once */
const [isExercisesLoaded, setExercisesLoaded] = useState(false);
const isExercisesLoaded = useRef(false);
/* Only render Card if innerWidth > 576px (small breakpoint) */
const [toRenderCard, setRenderCard] = useState(true);
@ -50,13 +50,13 @@ export default function ExercisesPage() {
setExercises(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
if (!isExercisesLoaded) {
if (!isExercisesLoaded.current) {
getExercises();
setExercisesLoaded(true);
isExercisesLoaded.current = true;
}
/* Get the user's favourites to bump them to the top of the exercise list */
if (isExercisesLoaded) {
if (isExercisesLoaded.current) {
if (authUser) {
const favs = exercises.filter((doc) =>
authUser.favouriteExercises.includes(doc.id)
@ -77,7 +77,7 @@ export default function ExercisesPage() {
if (window.innerWidth < 576) {
setRenderCard(false);
}
}, [authUser, selectedExercise, exerciseList, isExercisesLoaded, exercises]);
}, [authUser, exercises]);
const [isOpen, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
@ -88,14 +88,18 @@ export default function ExercisesPage() {
* If the window width is small, the modal is opened when the elements are
* clicked. Otherwise, the card is updated.
*/
const onClick = () => {
const onClick = (ex) => {
setSelectedExercise(ex);
if (!toRenderCard) {
handleOpen();
} else {
setSelectedExercise(selectedExercise.id);
}
};
/* When an exercise is deleted, remove it from the list. */
const onDelete = (id) => {
setExerciseList(exercises.filter((doc) => doc.id !== id));
};
return (
<>
<TopNavbar />
@ -128,9 +132,9 @@ export default function ExercisesPage() {
list={exerciseList}
listType="radio"
selected={selectedExercise}
setSelected={setSelectedExercise}
setSelected={onClick}
type="exercises"
onClick={onClick}
onDelete={onDelete}
/>
</Col>
</Row>

View File

@ -78,7 +78,7 @@ export default function WorkoutsPage() {
if (window.innerWidth < 576) {
setRenderCard(false);
}
}, [authUser, isWorkoutsLoaded, selected, workoutList, workouts]);
}, [authUser, isWorkoutsLoaded, workouts]);
const [isOpen, setOpen] = useState(false);
const handleOpen = () => setOpen(true);

View File

@ -4,6 +4,7 @@
.form {
padding: 0 10vw;
margin-bottom: 5vh;
}
.toast {