merge main into feature-exercise-crud

This commit is contained in:
Rory Healy 2022-10-17 07:12:05 +11:00
commit 2bb053ee97
37 changed files with 1888 additions and 702 deletions

View File

@ -4,36 +4,10 @@ import React from 'react';
// Styles
import styles from '../styles/Instructions.module.css';
export default function Instructions() {
export default function Instructions({ text }) {
return (
<div className={styles.instructions}>
<p>
<span className={styles.line}>
Step 1: Stand up straight with your torso upright. Hold a dumbbell in
each hand at arms-length. Your elbows should be close to your torso.
</span>
<span className={styles.line}>
Step 2: The palms of your hands should be facing your torso. This is
the starting position for the exercise.
</span>
<span className={styles.line}>
Step 3: Curl the weight forward while contracting your biceps. Your
upper arm should remain stationary. Continue to lift the weight until
your biceps are fully contracted and the dumbbell is at shoulder
level. Hold the contraction for a moment as you squeeze your biceps.
</span>
<span className={styles.line}>
Step 4: Inhale and slowly start to bring the dumbbells back to the
starting position.
</span>
<span className={styles.line}>
Step 5: Repeat for the desired number of reps.
</span>
</p>
<span className={styles.line}>{text}</span>
</div>
);
}

View File

@ -1,30 +1,115 @@
// React
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
// Next components
import Image from 'next/image';
// Firebase
import { collection, updateDoc, doc } from 'firebase/firestore';
import { db } from '../../firebase-config';
// Custom components
import CRUDButton from '../CRUDButton/CRUDButton';
// Styles
import styles from '../../styles/List.module.css';
// Star images
// Authentication
import { useAuth } from '../../context/authUserContext';
const star = '/images/star.png';
const starFilled = '/images/starFilled.png';
export default function Element({ element }) {
const usersCollectionRef = collection(db, 'users');
export default function Element({ element, type }) {
// State of the image that is displayed as the favourite button
const [imgPath, setImgPath] = useState(star);
const { authUser } = useAuth();
useEffect(() => {
const isChecked = () => {
if (authUser) {
if (type === 'workouts') {
if (authUser.favouriteWorkouts.includes(element.id)) {
setImgPath(starFilled);
} else {
setImgPath(star);
}
}
if (type === 'exercises') {
if (authUser.favouriteExercises.includes(element.id)) {
setImgPath(starFilled);
} else {
setImgPath(star);
}
}
} else {
setImgPath(star);
}
};
isChecked();
}, [authUser, element.id, type]);
const updateFavWorkouts = async (newFavs) => {
authUser.favouriteWorkouts = newFavs;
const docRef = doc(usersCollectionRef, authUser.uid);
await updateDoc(docRef, {
favouriteWorkouts: newFavs,
});
};
const updateFavExercises = async (newFavs) => {
authUser.favouriteExercises = newFavs;
const docRef = doc(usersCollectionRef, authUser.uid);
await updateDoc(docRef, {
favouriteExercises: newFavs,
});
};
const removeFavWorkout = (elem) => {
const newFavs = authUser.favouriteWorkouts.filter((val) => val !== elem);
updateFavWorkouts(newFavs);
};
const addToFavWorkouts = async (elem) => {
authUser.favouriteWorkouts.push(elem);
updateFavWorkouts(authUser.favouriteWorkouts);
};
const removeFavExercise = async (elem) => {
const newFavs = authUser.favouriteExercises.filter((val) => val !== elem);
updateFavExercises(newFavs);
};
const addToFavExercises = async (elem) => {
authUser.favouriteExercises.push(elem);
updateFavExercises(authUser.favouriteExercises);
};
// Event handler if the favourite button is clicked on
const toggleStar = (e) => {
e.preventDefault();
if (imgPath === star) {
setImgPath(starFilled);
} else {
setImgPath(star);
if (authUser) {
if (type === 'workouts') {
if (authUser.favouriteWorkouts.includes(element.id)) {
removeFavWorkout(element.id);
setImgPath(star);
} else {
addToFavWorkouts(element.id);
setImgPath(starFilled);
}
}
if (type === 'exercises') {
if (authUser.favouriteExercises.includes(element.id)) {
removeFavExercise(element.id);
setImgPath(star);
} else {
addToFavExercises(element.id);
setImgPath(starFilled);
}
}
}
};

View File

@ -3,16 +3,18 @@ import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
// Custom components
import Element from './Element';
import Workout from '../../public/classes/Workout';
import ElementTest from '../Test/ElementTest';
// Test Data
import { workouts } from '../../testData/testData';
const toggleImgPath = (src) =>
src.includes('star.png') ? '/images/starFilled.png' : '/images/star.png';
describe('The Favourite button', () => {
it('Toggles on and off when clicked', () => {
const element = new Workout('Pull Workout', ['Back', 'Biceps', 'Abs']);
render(<Element element={element} />);
const element = workouts[0];
render(<ElementTest element={element} type="workouts" />);
const favBtn = screen.getByRole('button');
let expectedImgPath = toggleImgPath(favBtn.getAttribute('src'));

View File

@ -9,6 +9,7 @@ import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';
import SearchFilterBar from './SearchFilterBar';
import Element from './Element';
import styles from '../../styles/List.module.css';
import SelectedElement from './SelectedElement';
/**
*
@ -18,7 +19,7 @@ import styles from '../../styles/List.module.css';
* @param {*} setSelected The function that sets the state of selected
* @returns
*/
export default function List({ list, listType, selected, setSelected }) {
export default function List({ list, listType, selected, setSelected, type }) {
// A function to handle when a new element is selected
const handleChange = (e) => {
setSelected(e);
@ -53,13 +54,17 @@ export default function List({ list, listType, selected, setSelected }) {
>
{filteredList.map((element) => (
<ToggleButton
key={element.name}
id={`${listType}-${element.name}`}
key={element.id}
id={`${listType}-${element.id}`}
variant="light"
name={listType}
value={element.name}
value={element.id}
>
<Element element={element} />
{selected === element.name ? (
<SelectedElement element={element} type={type} />
) : (
<Element element={element} type={type} />
)}
</ToggleButton>
))}
</ToggleButtonGroup>

View File

@ -5,6 +5,8 @@ import React from 'react';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import styles from '../../styles/Search.module.css';
export default function SearchFilterBar({ setSearchInput }) {
// Function to handle when the search input changes
const handleSearchInput = (e) => {
@ -20,8 +22,11 @@ export default function SearchFilterBar({ setSearchInput }) {
onChange={handleSearchInput}
/>
<Form.Select aria-label="Default select example">
<option>Filter Muscle Group</option>
<Form.Select
aria-label="Default select example"
className={styles.filter}
>
<option>Filter</option>
<option value="1">Chest</option>
<option value="2">Back</option>
<option value="3">Hamstrings</option>

View File

@ -1,17 +1,10 @@
// React
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import List from './List';
import Workout from '../../public/classes/Workout';
import ListTest from '../Test/ListTest';
// Dummy data to render in List component
const workoutList = [];
workoutList.push(
new Workout('Push Workout', ['Chest', 'Shoulder', 'Triceps']),
new Workout('Pull Workout', ['Back', 'Biceps', 'Abs']),
new Workout('Legs Workout', ['Quadriceps', 'Hamstrings', 'Calves']),
new Workout('Upper Workout', ['Chest', 'Back', 'Shoulder', 'Triceps'])
);
import { workouts } from '../../testData/testData';
describe('The Search Bar', () => {
it('filters the correct items on lowercase input "pu"', () => {
@ -20,8 +13,8 @@ describe('The Search Bar', () => {
const setSelected = jest.fn();
const list = render(
<List
list={workoutList}
<ListTest
list={workouts}
selected={selected}
setSelected={setSelected}
type="radio"
@ -48,8 +41,8 @@ describe('The Search Bar', () => {
const setSelected = jest.fn();
const list = render(
<List
list={workoutList}
<ListTest
list={workouts}
selected={selected}
setSelected={setSelected}
type="radio"
@ -77,8 +70,8 @@ describe('The Search Bar', () => {
const setSelected = jest.fn();
const list = render(
<List
list={workoutList}
<ListTest
list={workouts}
selected={selected}
setSelected={setSelected}
type="radio"
@ -94,10 +87,10 @@ describe('The Search Bar', () => {
const items = list.getAllByRole('radio');
// Expect only 'Push Workout' and 'Pull Workout to remain
expect(items.length).toBe(4);
expect(items[0].getAttribute('value') === 'Push Workout').toBeTruthy();
expect(items[1].getAttribute('value') === 'Pull Workout').toBeTruthy();
expect(items[2].getAttribute('value') === 'Legs Workout').toBeTruthy();
expect(items[3].getAttribute('value') === 'Upper Workout').toBeTruthy();
expect(items.length).toBe(8);
expect(items[4].getAttribute('value') === 'Push Workout').toBeTruthy();
expect(items[5].getAttribute('value') === 'Pull Workout').toBeTruthy();
expect(items[6].getAttribute('value') === 'Legs Workout').toBeTruthy();
expect(items[7].getAttribute('value') === 'Upper Workout').toBeTruthy();
});
});

View File

@ -0,0 +1,133 @@
// Import React
import React, { useEffect, useState } from 'react';
// Firebase
import { collection, doc, updateDoc } from 'firebase/firestore';
import { db } from '../../firebase-config';
// Authentication
import { useAuth } from '../../context/authUserContext';
// Styles
import styles from '../../styles/List.module.css';
// Get reference to users collection
const usersCollectionRef = collection(db, 'users');
const star = '/images/star.png';
const starFilled = '/images/starFilled.png';
// Element returns what should be displayed for each element of the list
export default function Element({ element, type }) {
// State of the image that is displayed as the favourite button
const [imgPath, setImgPath] = useState(star);
const { authUser } = useAuth();
useEffect(() => {
const isChecked = () => {
if (authUser) {
if (type === 'workouts') {
if (authUser.favouriteWorkouts.includes(element.id)) {
setImgPath(starFilled);
} else {
setImgPath(star);
}
}
if (type === 'exercises') {
if (authUser.favouriteExercises.includes(element.id)) {
setImgPath(starFilled);
} else {
setImgPath(star);
}
}
} else {
setImgPath(star);
}
};
isChecked();
}, [authUser, element.id, type]);
const updateFavWorkouts = async (newFavs) => {
authUser.favouriteWorkouts = newFavs;
const docRef = doc(usersCollectionRef, authUser.uid);
await updateDoc(docRef, {
favouriteWorkouts: newFavs,
});
};
const updateFavExercises = async (newFavs) => {
authUser.favouriteExercises = newFavs;
const docRef = doc(usersCollectionRef, authUser.uid);
await updateDoc(docRef, {
favouriteExercises: newFavs,
});
};
const removeFavWorkout = (elem) => {
const newFavs = authUser.favouriteWorkouts.filter((val) => val !== elem);
updateFavWorkouts(newFavs);
};
const addToFavWorkouts = async (elem) => {
authUser.favouriteWorkouts.push(elem);
updateFavWorkouts(authUser.favouriteWorkouts);
};
const removeFavExercise = async (elem) => {
const newFavs = authUser.favouriteExercises.filter((val) => val !== elem);
updateFavExercises(newFavs);
};
const addToFavExercises = async (elem) => {
authUser.favouriteExercises.push(elem);
updateFavExercises(authUser.favouriteExercises);
};
// Event handler if the favourite button is clicked on
const toggleStar = (e) => {
e.preventDefault();
if (authUser) {
if (type === 'workouts') {
if (authUser.favouriteWorkouts.includes(element.id)) {
removeFavWorkout(element.id);
setImgPath(star);
} else {
addToFavWorkouts(element.id);
setImgPath(starFilled);
}
}
if (type === 'exercises') {
if (authUser.favouriteExercises.includes(element.id)) {
removeFavExercise(element.id);
setImgPath(star);
} else {
addToFavExercises(element.id);
setImgPath(starFilled);
}
}
}
};
return (
<div className={styles.selement}>
<div className={styles.stxt}>
<h1>{element.name}</h1>
<p>{element.muscleGroups.join(', ')}</p>
</div>
<div className={styles.star}>
<form>
<input
type="image"
src={imgPath}
height={38}
width={38}
alt="star"
onClick={toggleStar}
/>
</form>
</div>
</div>
);
}

View File

@ -16,6 +16,7 @@ import SignInView from '../Profile/SignInView';
// Styles
import styles from '../../styles/Navbar.module.css';
// User Authentication
import { useAuth } from '../../context/authUserContext';
export default function TopNavbar() {

View File

@ -3,7 +3,7 @@ import React, { useState } from 'react';
// Bootstrap Components
import Nav from 'react-bootstrap/Nav';
import Figure from 'react-bootstrap/Figure';
import Image from 'react-bootstrap/Image';
import Offcanvas from 'react-bootstrap/Offcanvas';
// Next Components
@ -13,7 +13,7 @@ import Link from 'next/link';
import { useAuth } from '../../context/authUserContext';
// Custom Components
import SettingsView from './SettingsView';
// import SettingsView from './SettingsView';
// Styles
import styles from '../../styles/Profile.module.css';
@ -34,7 +34,7 @@ export default function ProfileView() {
<>
<Nav onClick={handleShow}>Profile</Nav>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas show={show} onHide={handleClose} placement="end">
<Offcanvas.Header closeButton>
<Offcanvas.Title>Profile</Offcanvas.Title>
</Offcanvas.Header>
@ -43,35 +43,28 @@ export default function ProfileView() {
<div className={styles.container}>
<main className={styles.main}>
<h1>{authUser.name}</h1>
<Figure>
<Figure.Image
width={200}
height={200}
alt="200x200"
src={authUser.photoURL}
/>
</Figure>
<Image
src={authUser.photoURL}
width={150}
height={150}
roundedCircle="true"
/>
<div className={styles.grid}>
<div className={styles.card}>
<Link href="/" className={styles.card}>
<p>Your favourites</p>
</Link>
</div>
<div className={styles.card}>
<Link href="/workouts" className={styles.card}>
<p>Your workouts</p>
</Link>
</div>
<div>
{/* Waiting for email/pword sign in */}
{/* <div>
<Nav.Link className={styles.card}>
<p>
<SettingsView />
</p>
</Nav.Link>
</div>
</div> */}
<div>
<Nav.Link className={styles.card} onClick={handleSignOut}>

View File

@ -6,14 +6,20 @@ import Nav from 'react-bootstrap/Nav';
import Offcanvas from 'react-bootstrap/Offcanvas';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import Image from 'react-bootstrap/Image';
import { FloatingLabel } from 'react-bootstrap';
// Firebase
import { useAuth } from '../../context/authUserContext';
// Styles
import styles from '../../styles/Settings.module.css';
export default function SettingsView() {
const [show, setShow] = useState(false);
const { authUser } = useAuth();
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
@ -21,7 +27,7 @@ export default function SettingsView() {
<>
<Nav onClick={handleShow}>Settings</Nav>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas show={show} onHide={handleClose} placement="end">
<Offcanvas.Header closeButton>
<Offcanvas.Title>Settings</Offcanvas.Title>
</Offcanvas.Header>
@ -29,11 +35,20 @@ export default function SettingsView() {
<div className={styles.container}>
<main className={styles.main}>
<Form>
<h3>
<Form.Label>Change Name</Form.Label>
</h3>
<h4>
<Form.Label>Update Profile Picture</Form.Label>
</h4>
<div className={styles.form}>
<Image src={authUser.photoURL} roundedCircle="true" />
<Form.Group controlId="formFile" className="mb-3">
<Form.Control type="file" size="sm" />
</Form.Group>
</div>
<div className={styles.item}>
<h4>
<Form.Label>Change Name</Form.Label>
</h4>
<div className={styles.form}>
<FloatingLabel label="First Name" className="mb-3">
<Form.Control placeholder="Alice" />
</FloatingLabel>
@ -42,11 +57,10 @@ export default function SettingsView() {
</FloatingLabel>
</div>
<h3>
<h4>
<Form.Label>Change Password</Form.Label>
</h3>
<div className={styles.item}>
</h4>
<div className={styles.form}>
<FloatingLabel label="Enter New Password" className="mb-3">
<Form.Control type="password" />
</FloatingLabel>
@ -55,9 +69,9 @@ export default function SettingsView() {
</FloatingLabel>
</div>
<div className={styles.item}>
<div className={styles.form}>
<Button variant="primary" type="submit" onClick={handleClose}>
Confirm
Save Changes
</Button>
</div>
</Form>

View File

@ -34,7 +34,7 @@ export default function SignInView() {
<>
<Nav onClick={handleShow}>Sign in</Nav>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas show={show} onHide={handleClose} placement="end">
<Offcanvas.Header closeButton>
<Offcanvas.Title />
</Offcanvas.Header>
@ -63,15 +63,17 @@ export default function SignInView() {
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
name="email"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicCheckbox">
{/* Waiting for email/pword sign in */}
{/* <Form.Group className="mb-3" controlId="formBasicCheckbox">
<Form.Check type="checkbox" label="Remember me" />
</Form.Group>
</Form.Group> */}
</div>
</div>

View File

@ -0,0 +1,55 @@
// React
import React, { useState } from 'react';
// Next components
import Image from 'next/image';
// Styles
import styles from '../../styles/List.module.css';
const star = '/images/star.png';
const starFilled = '/images/starFilled.png';
export default function ElementTest({ element }) {
// State of the image that is displayed as the favourite button
const [imgPath, setImgPath] = useState(star);
// Event handler if the favourite button is clicked on
const toggleStar = (e) => {
e.preventDefault();
if (imgPath === star) {
setImgPath(starFilled);
} else {
setImgPath(star);
}
};
return (
<div className={styles.element}>
<Image
src={element.imgSrc}
alt={element.imgAlt}
height={84}
width={120}
/>
<div className={styles.txt}>
<h1>{element.name}</h1>
<p>{element.muscleGroups.join(', ')}</p>
</div>
<div className={styles.star}>
<form>
<input
type="image"
src={imgPath}
height={38}
width={38}
alt="star"
onClick={toggleStar}
/>
</form>
</div>
</div>
);
}

View File

@ -0,0 +1,69 @@
// React
import React, { useState } from 'react';
// Bootstrap components
import ToggleButton from 'react-bootstrap/ToggleButton';
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';
// Custom components
import SearchFilterBar from '../List/SearchFilterBar';
import ElementTest from './ElementTest';
import styles from '../../styles/List.module.css';
/**
*
* @param {*} list A list of either workouts or exercises
* @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
* @returns
*/
export default function List({ list, listType, selected, setSelected, type }) {
// A function to handle when a new element is selected
const handleChange = (e) => {
setSelected(e);
};
// State to keep track of the search input
const [searchInput, setSearchInput] = useState('');
// When searchInput is changed, filteredList updates to only contain elements with names including searchInput
const filteredList = list.filter((item) => {
if (searchInput === '') {
return item;
}
return item.name.toLowerCase().includes(searchInput);
});
return (
<div className={styles.container}>
<SearchFilterBar
searchInput={searchInput}
setSearchInput={setSearchInput}
/>
{/* The list. A group of toggle buttons, so that the active one can be kept track of */}
<div className={styles.scrollableContainer}>
<ToggleButtonGroup
type={listType}
value={selected}
onChange={handleChange}
vertical
name="button-list"
>
{filteredList.map((element) => (
<ToggleButton
key={element.name}
id={`${listType}-${element.name}`}
variant="light"
name={listType}
value={element.name}
>
<ElementTest element={element} type={type} />
</ToggleButton>
))}
</ToggleButtonGroup>
</div>
</div>
);
}

View File

@ -0,0 +1,43 @@
// Import React
import React from 'react';
// Next components
import Image from 'next/image';
import { Container, Row, Col } from 'react-bootstrap';
// Styles
import styles from '../../styles/Workouts/WorkoutList.module.css';
// Element returns what should be displayed for each element of the list
export default function WorkoutElement({ element }) {
// console.log(element)
return (
<Container className={styles.workoutexerciselist}>
<Col>
<div className={styles.element}>
<Image
src={element.imgSrc}
alt={element.imgAlt}
height={40}
width={65}
/>
<div className={styles.txt}>
<p>{element.name}</p>
</div>
</div>
</Col>
<Col>
<Row>
<div className={styles.sr}>
<p>{element.sets} sets</p>
</div>
</Row>
<Row>
<div className={styles.sr}>
<p>{element.reps} reps</p>
</div>
</Row>
</Col>
</Container>
);
}

View File

@ -0,0 +1,39 @@
// React
import React from 'react';
// Bootstrap Components
import Button from 'react-bootstrap/Button';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
// Custom components
import WorkoutElement from './WorkoutElement';
// Styles
import styles from '../../styles/Workouts/WorkoutList.module.css';
export default function WorkoutList({ exerciseList }) {
return (
<div>
{/* The list. A group of toggle buttons, so that the active one can be kept track of */}
<div className={styles.scrollableContainer}>
<ButtonGroup
// onChange={onchange}
variant="secondary"
vertical
name="button-list"
>
{exerciseList.map((element) => (
<Button
className={styles.list}
key={element.id}
id={`radio-${element.id}`}
value={element.id}
>
<WorkoutElement element={element} />
</Button>
))}
</ButtonGroup>
</div>
</div>
);
}

View File

@ -1,13 +1,21 @@
// React
import React from 'react';
export default function YouTube() {
return (
<iframe
width="100%"
height="60%"
src="https://www.youtube.com/embed/TwD-YGVP4Bk"
title="Exercise video"
/>
);
function verifyURL(link) {
const regExp =
/^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
const match = link.match(regExp);
return match && match[7].length === 11 ? match[7] : false;
}
export default function YouTube({ link }) {
if (link) {
return (
<iframe
width="100%"
height="60%"
src={`https://www.youtube.com/embed/${verifyURL(link)}`}
title="Exercise video"
/>
);
}
}

View File

@ -1,5 +1,5 @@
// React
import { createContext, useContext } from 'react';
import React, { createContext, useContext } from 'react';
// Firebase
import useFirebaseAuth from '../lib/useFirebaseAuth';

View File

@ -1,14 +1,7 @@
// import firebase SDK
import { initializeApp } from 'firebase/app';
import {
getFirestore,
getDoc,
doc,
collection,
setDoc,
} from 'firebase/firestore';
import { getAuth, signInWithRedirect, getRedirectResult } from 'firebase/auth';
import { GoogleAuthProvider } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getAuth, GoogleAuthProvider } from 'firebase/auth';
// store configuration details
const firebaseConfig = {
@ -26,44 +19,3 @@ const app = initializeApp(firebaseConfig);
export const db = getFirestore();
export const auth = getAuth(app);
export const provider = new GoogleAuthProvider();
const usersCollectionRef = collection(db, 'users');
// Firebase features
export const signInWithGoogle = async () => {
signInWithRedirect(auth, provider);
getRedirectResult(auth)
.then(async (result) => {
// This gives you a Google Access Token.
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
console.log(result);
// The signed-in user info.
const user = result.user;
const docRef = doc(db, usersCollectionRef, user.uid);
const userDoc = await getDoc(docRef);
if (!userDoc.exists()) {
createNewUser(user);
}
})
.catch((error) => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
// The email of the user's account used.
const email = error.customData.email;
// The AuthCredential type that was used.
const credential = GoogleAuthProvider.credentialFromError(error);
// ...
});
};
const createNewUser = async (user) => {
await setDoc(doc(db, usersCollectionRef, user.uid), {
name: user.displayName,
photo: user.photoURL,
role: 1,
});
};

View File

@ -11,11 +11,14 @@ import { auth, provider, db } from '../firebase-config';
const usersCollectionRef = collection(db, 'users');
const formatAuthUser = (user) => ({
const formatAuthUser = (user, id) => ({
uid: id,
name: user.name,
email: user.email,
photoURL: user.photo,
role: user.role,
favouriteWorkouts: user.favouriteWorkouts,
favouriteExercises: user.favouriteExercises,
});
const createNewUser = async (user) => {
@ -24,6 +27,8 @@ const createNewUser = async (user) => {
email: user.email,
photo: user.photoURL,
role: 1,
favouriteWorkouts: [],
favouriteExercises: [],
});
};
@ -44,7 +49,7 @@ export default function useFirebaseAuth() {
createNewUser(user);
userDoc = await getDoc(docRef);
}
const formattedUser = formatAuthUser(userDoc.data());
const formattedUser = formatAuthUser(userDoc.data(), user.uid);
setAuthUser(formattedUser);
setLoading(false);
};

1125
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
},
"dependencies": {
"bootstrap": "^5.2.1",
"firebase": "^9.10.0",
"firebase": "^9.12.1",
"next": "12.3.0",
"prop-types": "^15.8.1",
"react": "18.2.0",

View File

@ -0,0 +1,37 @@
// React
import React, { useEffect, useState } from 'react';
// Bootstrap components
import ListTest from '../components/Test/ListTest';
// Styles
import styles from '../styles/Workouts/Workouts.module.css';
// Authentication
import { exercises } from '../testData/testData';
export default function ExercisesPage() {
const [exerciseList, setExerciseList] = useState([]);
useEffect(() => {
const getExercises = () => {
setExerciseList(exercises);
};
getExercises();
}, []);
const [selected, setSelected] = useState('');
return (
<div className={styles.container}>
<main className={styles.main}>
<ListTest
list={exerciseList}
listType="radio"
selected={selected}
setSelected={setSelected}
type="workouts"
/>
</main>
</div>
);
}

View File

@ -0,0 +1,37 @@
// React
import React, { useEffect, useState } from 'react';
// Bootstrap components
import ListTest from '../components/Test/ListTest';
// Styles
import styles from '../styles/Workouts/Workouts.module.css';
// Authentication
import { workouts } from '../testData/testData';
export default function WorkoutsPage() {
const [workoutList, setWorkoutList] = useState([]);
useEffect(() => {
const getWorkouts = () => {
setWorkoutList(workouts);
};
getWorkouts();
}, []);
const [selected, setSelected] = useState('');
return (
<div className={styles.container}>
<main className={styles.main}>
<ListTest
list={workoutList}
listType="radio"
selected={selected}
setSelected={setSelected}
type="workouts"
/>
</main>
</div>
);
}

View File

@ -1,50 +1,9 @@
// React
import React, { useState } from 'react';
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
// Custom components
import List from '../components/List/List';
// Styles
import styles from '../styles/Exercises.module.css';
// Import the Exercise class so that we can create a dummy set of exercises to render
import Exercise from '../public/classes/Exercise';
function ExercisesTest() {
// A dummy exercise list so that we have data to render.
// Once the database is implemented this will not be necessary
const exerciseList = [];
exerciseList.push(
new Exercise('Bench Press', ['Chest', 'Shoulder', 'Triceps']),
new Exercise('Squats', ['Quadriceps', 'Hamstrings', 'Calves', 'Glutes']),
new Exercise('Plank', [
'Quadriceps',
'Hamstrings',
'Core',
'Triceps',
'Glutes',
]),
new Exercise('Bench Dips ', ['Chest', 'Triceps']),
new Exercise('Lunges', ['Hamstrings', 'Glutes', 'Quadriceps', 'Calves']),
new Exercise('Custom exercise 1', ['Back', 'Biceps', 'Abs']),
new Exercise('Custom exercise 2', ['Quadriceps', 'Hamstrings', 'Calves']),
new Exercise('Custom exercise 3', ['Chest', 'Back', 'Shoulder', 'Triceps'])
);
const selectState = {};
[selectState.selected, selectState.setSelected] = useState();
return (
<>
<div className={styles.container}>
<main className={styles.main}>
<List list={exerciseList} {...selectState} />
</main>
</div>
</>
);
}
// Custom Components
import ExercisesTest from './ExercisesTest';
describe('The List of buttons displaying Workouts or Exercises', () => {
it('Updates the selected button when another is clicked', () => {

View File

@ -1,44 +1,9 @@
// React
import React, { useState } from 'react';
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
// Custom Components
//import WorkoutsPage from '../pages/workouts/index';
import Workout from '../public/classes/Workout';
import List from '../components/List/List';
import styles from '../styles/Workouts.module.css';
function WorkoutsTest() {
// A dummy workout list so that we have data to render.
// Once the database is implemented this will not be necessary
const workoutList = [];
workoutList.push(
new Workout('Push Workout', ['Chest', 'Shoulder', 'Triceps']),
new Workout('Pull Workout', ['Back', 'Biceps', 'Abs']),
new Workout('Legs Workout', ['Quadriceps', 'Hamstrings', 'Calves']),
new Workout('Upper Workout', ['Chest', 'Back', 'Shoulder', 'Triceps']),
new Workout('Workout 1', ['Chest', 'Shoulder', 'Triceps']),
new Workout('Workout 2', ['Back', 'Biceps', 'Abs']),
new Workout('Workout 3', ['Quadriceps', 'Hamstrings', 'Calves']),
new Workout('Workout 4', ['Chest', 'Back', 'Shoulder', 'Triceps'])
);
const selectState = {};
[selectState.selected, selectState.setSelected] = useState('');
return (
<>
<div className={styles.container}>
<main className={styles.main}>
<List list={workoutList} listType="radio" {...selectState} />
</main>
</div>
</>
);
}
import WorkoutsTest from './WorkoutsTest';
describe('The List of buttons displaying Workouts or Exercises', () => {
it('Updates the selected button when another is clicked', () => {

View File

@ -1,47 +1,105 @@
/* eslint-disable react/jsx-props-no-spreading */
// React
import React, { useState, useEffect } from 'react';
// Bootstrap components
import Col from 'react-bootstrap/Col';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
// Firebase
import { getDocs, collection, query, orderBy, limit } from 'firebase/firestore';
import { db } from '../../firebase-config';
// Custom components
import CRUDButton from '../../components/CRUDButton/CRUDButton';
import Instructions from '../../components/Instructions';
import List from '../../components/List/List';
import TopNavbar from '../../components/Navbar/Navbar';
import YouTube from '../../components/YouTube';
// Styles
import styles from '../../styles/Exercises.module.css';
// Get reference to workouts collection
// Authentication
import { useAuth } from '../../context/authUserContext';
// Get reference to exercises collection
const exercisesCollectionRef = collection(db, 'exercises');
export default function ExercisesPage() {
const [exerciseList, setExerciseList] = useState([]);
const [selectedExercise, setSelectedExercise] = useState();
const { authUser } = useAuth();
useEffect(() => {
const getExercises = async () => {
const q = query(exercisesCollectionRef, orderBy('name'), limit(10));
const data = await getDocs(q);
setExerciseList(
data.docs.map((document) => ({ ...document.data(), id: document.id }))
);
const exercises = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
if (authUser) {
const favs = exercises.filter((doc) =>
authUser.favouriteExercises.includes(doc.id)
);
const unfavs = exercises.filter(
(doc) => !authUser.favouriteExercises.includes(doc.id)
);
const finalList = favs.concat(unfavs);
setSelectedExercise(finalList[0]);
setExerciseList(finalList);
} else {
setSelectedExercise(exercises[0]);
setExerciseList(exercises);
}
};
getExercises();
}, []);
}, [authUser]);
const selectState = {};
[selectState.selected, selectState.setSelected] = useState();
const [selected, setSelected] = useState('');
useEffect(() => {
const getSelected = () => {
if (selected) {
exerciseList.forEach((doc) => {
if (doc.id === selected) {
setSelectedExercise(doc);
}
});
}
};
getSelected();
}, [selected, exerciseList]);
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<CRUDButton type="exercise" create />
<List list={exerciseList} listType="radio" {...selectState} />
</main>
</div>
<Container className={styles.container}>
<CRUDButton type="exercise" create />
<Row>
<Col>
{selectedExercise != null && (
<YouTube link={selectedExercise.videoURL} />
)}
{selectedExercise != null && (
<Instructions text={selectedExercise.instructions} />
)}
</Col>
<Col>
<div>
<main className={styles.main}>
<List
list={exerciseList}
listType="radio"
selected={selected}
setSelected={setSelected}
type="exercises"
/>
</main>
</div>
</Col>
</Row>
</Container>
</>
);
}

View File

@ -1,50 +1,125 @@
// 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';
// Next components
import Image from 'next/image';
// Firebase
import { getDocs, collection, query, orderBy, limit } from 'firebase/firestore';
import { db } from '../../firebase-config';
// Bootstrap components
// Custom components
import CRUDButton from '../../components/CRUDButton/CRUDButton';
import List from '../../components/List/List';
import TopNavbar from '../../components/Navbar/Navbar';
import WorkoutList from '../../components/WorkoutList/WorkoutList';
// Styles
import styles from '../../styles/Workouts.module.css';
import styles from '../../styles/Workouts/Workouts.module.css';
// Import the database reference and functions for reading from firestore
// Authentication
import { useAuth } from '../../context/authUserContext';
// Get reference to workouts collection
const workoutsCollectionRef = collection(db, 'workouts');
export default function WorkoutsPage() {
const [workoutList, setWorkoutList] = useState([]);
const { authUser } = useAuth();
const [selected, setSelected] = useState('');
const [selectedWorkout, setSelectedWorkout] = useState();
useEffect(() => {
const getWorkouts = async () => {
const q = query(workoutsCollectionRef, orderBy('name'), limit(10));
const data = await getDocs(q);
setWorkoutList(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
const workouts = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
if (authUser) {
const favs = workouts.filter((doc) =>
authUser.favouriteWorkouts.includes(doc.id)
);
const unfavs = workouts.filter(
(doc) => !authUser.favouriteWorkouts.includes(doc.id)
);
const finalList = favs.concat(unfavs);
setSelectedWorkout(finalList[0]);
setWorkoutList(finalList);
} else {
setSelectedWorkout(workouts[0]);
setWorkoutList(workouts);
}
};
getWorkouts();
}, []);
}, [authUser]);
const [selected, setSelected] = useState('');
useEffect(() => {
const getSelected = () => {
workoutList.forEach((doc) => {
if (doc.id === selected) {
setSelectedWorkout(doc);
}
});
};
getSelected();
}, [selected, workoutList]);
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<CRUDButton type="workout" create />
<List
list={workoutList}
listType="radio"
selected={selected}
setSelected={setSelected}
/>
</main>
</div>
<Container className={styles.container}>
<CRUDButton type="workout" create />
<Row>
<Col>
{selectedWorkout != null && (
<div className={styles.imgcontainer}>
<Image
src={selectedWorkout.imgSrc}
alt="workout image"
width="0"
height="0"
sizes="100vw"
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>
<div>
<main className={styles.main}>
<List
list={workoutList}
listType="radio"
selected={selected}
setSelected={setSelected}
type="workouts"
/>
</main>
</div>
</Col>
</Row>
</Container>
</>
);
}

View File

@ -2,13 +2,15 @@ export default class Exercise {
/**
* A class to represent Exercise.
* @param {String} name
* @param {[String]} muscles
* @param {[String]} muscleGroups
* @param {{Sets: Int, Reps: Int}} repRange
*/
constructor(name, muscles) {
constructor(name, muscleGroups, repRange) {
this.name = name;
this.muscles = muscles;
this.muscleGroups = muscleGroups;
this.repRange = repRange;
this.imgSrc =
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1';
'https://images.pexels.com/photos/2204196/pexels-photo-2204196.jpeg';
this.imgAlt = 'A man doing a bench press.';
}
}

View File

@ -3,12 +3,14 @@ class Workout {
* A class to represent Workouts.
* @param {String} name
* @param {[String]} muscleGroups
* * @param {[String]} exercises
*/
constructor(name, muscleGroups) {
constructor(name, muscleGroups, exercises) {
this.name = name;
this.muscleGroups = muscleGroups;
this.exercises = exercises;
this.imgSrc =
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1';
'https://images.pexels.com/photos/2204196/pexels-photo-2204196.jpeg';
this.imgAlt = 'A man doing a bench press.';
}
}

View File

@ -1,11 +1,11 @@
.container {
padding: 0 2rem;
margin: 50px;
padding: 15vh 0;
margin: 0 auto;
align-content: center;
}
.main {
min-height: 100vh;
padding: 4rem 0;
margin: 0 2vw;
flex: 1;
display: flex;
flex-direction: column;
@ -86,12 +86,12 @@
max-width: 300px;
}
.card:hover,
/* .card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
} */
.card h2 {
margin: 0 0 1rem 0;

View File

@ -1,11 +1,19 @@
.instructions {
border: 2px solid black;
border: 1px solid black;
border-radius: 10px;
padding: 0px 20px;
margin: 30px 0;
margin: 2vh 0;
height: 30vh;
display: flex;
flex-direction: column;
overflow-y: auto;
min-height: 0;
scrollbar-width: auto;
}
.line {
display: inline-block;
padding: 0.5em 0;
text-align: justify;
}

View File

@ -1,33 +1,87 @@
/* Holds the whole list and search filter bar */
.container {
margin-top: 3vh;
}
/* Makes the list scrollable if it overflows */
.scrollableContainer {
border: 1px solid black;
border-radius: 10px;
display: flex;
flex-direction: column;
height: 75vh;
height: 71vh;
overflow-y: auto;
min-height: 0;
padding: 1.5vw 1.5vw 0.5vw 1.5vw;
scrollbar-width: auto;
}
.list {
border: 1px solid black;
margin-bottom: 1vw;
background-color: white;
color: black;
}
.list:hover,
.list:active,
.list:checked {
background-color: #0070f3 !important;
color: white !important;
}
/* Contains the image and the text */
.element {
display: flex;
.element,
.selement {
display: inline-flex;
height: 14vh;
width: 25vw + 120px + 60px;
padding: 0.5vw;
}
.selement {
margin: 0 auto;
justify-content: center;
}
.star {
margin: auto;
display: block;
margin-left: 1vw;
}
/* text in list */
.txt h1 {
font-size: x-large;
margin-top: 10px;
margin-left: 5px;
.txt,
.stxt {
overflow: hidden;
white-space: nowrap;
}
.stxt {
text-align: center;
text-overflow: ellipsis;
width: 100%;
}
.stxt h1 {
font-size: xx-large;
margin: 10px 0 0 0;
}
.stxt p {
font-size: large;
font-style: italic;
font-weight: 300;
margin-bottom: 0;
}
/* text in list */
.txt {
text-align: left;
text-overflow: ellipsis;
width: 20vw;
overflow: hidden;
white-space: nowrap;
margin-left: 1vw;
}
.txt h1 {
font-size: xx-large;
margin: 10px 0 0 5px;
}
.txt p {
@ -36,16 +90,6 @@
font-weight: 300;
margin-left: 6px;
margin-bottom: 0;
text-align: left;
text-overflow: ellipsis;
width: 20vw;
overflow: hidden;
white-space: nowrap;
}
.star {
margin-top: 21px;
margin-left: 4px;
}
@media only screen and (max-width: 576px) {

View File

@ -1,22 +1,19 @@
.container {
padding-top: 10px;
padding-bottom: 10px;
/* padding-top: 10px; */
padding-bottom: 20px;
height: 11vh;
}
.filter {
background: #fff;
color: black;
border: 2px solid lightgray;
border-radius: 3px;
padding: 5px 10px;
width: 100%;
.filter,
.container input {
text-align: center;
font-size: 1.5vw;
}
.container input {
text-align: center;
background-image: url(../public/search.svg);
background-size: 8%;
background-position: 6vw 7px;
background-position: 5.2vw 2.5vh;
background-repeat: no-repeat;
text-indent: 20px;
}

View File

@ -4,7 +4,7 @@
.main {
min-height: 100vh;
padding: 4rem 0;
padding: 2rem 0;
flex: 1;
display: flex;
flex-direction: column;
@ -109,6 +109,7 @@
.buttons {
display: flex;
justify-content: center;
margin-bottom: 0.5rem;
}
.signup {
@ -119,6 +120,12 @@
width: 100%;
}
.form {
width: 100%;
text-align: center;
margin-bottom: 1rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;

View File

@ -0,0 +1,91 @@
/* Makes the list scrollable if it overflows */
.scrollableContainer {
border: 1px solid black;
border-radius: 10px;
display: flex;
flex-direction: column;
height: 30vh;
overflow-y: auto;
min-height: 0;
/* padding: 1.5vw 1.5vw 0.5vw 1.5vw; */
scrollbar-width: auto;
}
.workoutexerciselist {
display: flex;
}
.list {
border: 0;
background-color: white;
color: black;
}
.list:hover,
.list:active,
.list:checked {
border: 0 !important;
background-color: #adb5bd !important;
color: white !important;
}
/* Contains the image and the text */
.element {
display: flex;
width: 35vw;
}
.sr p {
margin-left: 0.5vw;
font-size: medium;
font-weight: 300;
margin-bottom: 0;
text-align: center;
text-overflow: ellipsis;
height: 100%;
overflow: hidden;
white-space: nowrap;
}
.txt p {
margin-left: 0.5vw;
font-size: xx-large;
font-weight: 300;
margin-bottom: 0;
text-align: center;
text-overflow: ellipsis;
width: 100%;
overflow: hidden;
white-space: nowrap;
}
@media only screen and (max-width: 576px) {
.element {
display: flex;
width: 80vw;
}
.txt h1 {
font-size: x-large;
margin-top: 10px;
margin-left: 5px;
text-align: left;
text-overflow: ellipsis;
width: 40vw;
overflow: hidden;
white-space: nowrap;
}
.txt p {
font-size: large;
font-style: italic;
font-weight: 300;
margin-left: 6px;
margin-bottom: 0;
text-align: left;
text-overflow: ellipsis;
width: 40vw;
overflow: hidden;
white-space: nowrap;
}
}

View File

@ -1,10 +1,12 @@
@import url(http://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900italic,900);
.container {
padding: 0 2rem;
padding: 15vh 0;
margin: 0 auto;
align-content: center;
}
.main {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;
flex-direction: column;
@ -12,6 +14,10 @@
align-items: center;
}
.workoutlist {
margin-top: 4vh;
}
.footer {
display: flex;
flex: 1;
@ -108,6 +114,36 @@
margin-left: 0.5rem;
}
.imgcontainer {
position: relative;
}
/* Bottom right text */
.textblock {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
padding-left: 20px;
padding-right: 20px;
overflow: hidden;
-webkit-text-stroke: 0.5px black;
}
.textblock h1 {
font-family: 'Roboto', sans-serif;
font-size: 7vh;
font-weight: bold;
margin: 10px 0 0 0;
}
.textblock p {
font-size: x-large;
font-style: italic;
font-weight: 400;
margin-bottom: 0;
}
@media (max-width: 600px) {
.grid {
width: 100%;
@ -115,7 +151,7 @@
}
}
/* @media (prefers-color-scheme: dark) {
@media (prefers-color-scheme: dark) {
.card,
.footer {
border-color: #222;
@ -126,4 +162,4 @@
.logo img {
filter: invert(1);
}
} */
}

107
testData/testData.jsx Normal file
View File

@ -0,0 +1,107 @@
export const exercises = [];
exercises.push({
name: 'Push Ups',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
muscleGroups: ['Biceps', 'Chest', 'Core'],
id: '0',
});
exercises.push({
name: 'Sit Ups',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
muscleGroups: ['Core'],
id: '1',
});
exercises.push({
name: 'Pull Ups',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
muscleGroups: ['Biceps', 'Shoulders'],
id: '2',
});
exercises.push({
name: 'Squats',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
muscleGroups: ['Legs', 'Glutes'],
id: '3',
});
export const workouts = [];
workouts.push({
name: 'Workout 1',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 0: [5, 10] }, { 1: [5, 10] }],
muscleGroups: ['Biceps', 'Chest', 'Core'],
id: '4',
});
workouts.push({
name: 'Workout 2',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 2: [5, 10] }, { 0: [5, 10] }, { 1: [5, 10] }],
muscleGroups: ['Biceps', 'Shoulders', 'Chest', 'Core'],
id: '5',
});
workouts.push({
name: 'Workout 3',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 2: [5, 10] }, { 3: [5, 10] }],
muscleGroups: ['Biceps', 'Shoulders', 'Legs', 'Glutes'],
id: '6',
});
workouts.push({
name: 'Workout 4',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 1: [5, 10] }, { 3: [5, 10] }],
muscleGroups: ['Core', 'Legs', 'Glutes'],
id: '7',
});
workouts.push({
name: 'Push Workout',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 0: [5, 10] }],
muscleGroups: ['Chest', 'Shoulder', 'Triceps'],
id: '8',
});
workouts.push({
name: 'Pull Workout',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 2: [5, 10] }],
muscleGroups: ['Back', 'Biceps', 'Abs'],
id: '9',
});
workouts.push({
name: 'Legs Workout',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 3: [5, 10] }],
muscleGroups: ['Quadriceps', 'Hamstrings', 'Calves'],
id: '10',
});
workouts.push({
name: 'Upper Workout',
imgSrc:
'https://images.pexels.com/photos/3837781/pexels-photo-3837781.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
exercises: [{ 2: [5, 10] }],
muscleGroups: ['Chest', 'Back', 'Shoulder', 'Triceps'],
id: '11',
});
const photo = '../public/profile-pic.jpg';
export const userAuth = {
uid: '12',
name: 'John Doe',
email: 'jdoe@gmail.com',
photoURL: photo,
role: 0,
favouriteWorkouts: ['5', '7'],
favouriteExercises: ['0', '3'],
};