Merge main into hayz-checkbox-list

This commit is contained in:
Rory Healy 2022-10-04 16:56:25 +11:00
commit 41219453f9
Signed by: roryhealy
GPG Key ID: 610ED1098B8F435B
42 changed files with 1763 additions and 266 deletions

View File

@ -1,3 +1,4 @@
.next
dist
node_modules/
.husky

View File

@ -9,6 +9,8 @@
"plugin:react/recommended",
"next/core-web-vitals",
"plugin:prettier/recommended",
"airbnb",
"airbnb/hooks",
"prettier"
],
"overrides": [],
@ -17,5 +19,7 @@
"sourceType": "module"
},
"plugins": ["react", "prettier"],
"rules": {}
"rules": {
"react/prop-types": [0]
}
}

View File

@ -20,5 +20,4 @@ jobs:
- run: npm ci
- run: npm run lint
- run: npm run prettier-check
- run: npm run prettier-fix

3
.gitignore vendored
View File

@ -37,3 +37,6 @@ next-env.d.ts
# development
/.vscode
# linting
/.husky

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install lint-staged

View File

@ -4,3 +4,4 @@ node_modules/
.vscode/
.github/
next.config.js
coverage

View File

@ -0,0 +1,39 @@
// React
import React from 'react';
// Styles
import styles from '../styles/Instructions.module.css';
export default function Instructions() {
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>
</div>
);
}

View File

@ -1,4 +1,4 @@
// Import React
// React
import React, { useState } from 'react';
// Next components
@ -10,15 +10,18 @@ import styles from '../../styles/List.module.css';
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 }) {
// State of the image that is displayed as the favorite button
// State of the image that is displayed as the favourite button
const [imgPath, setImgPath] = useState(star);
// Event handler if the favorite button is clicked on
// Event handler if the favourite button is clicked on
const toggleStar = (e) => {
e.preventDefault();
imgPath == star ? setImgPath(starFilled) : setImgPath(star);
if (imgPath === star) {
setImgPath(starFilled);
} else {
setImgPath(star);
}
};
return (
@ -44,7 +47,7 @@ export default function Element({ element }) {
width={38}
alt="star"
onClick={toggleStar}
></input>
/>
</form>
</div>
</div>

View File

@ -1,19 +1,20 @@
// React
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
// Custom components
import Element from './Element';
import Workout from '../../public/classes/Workout';
const toggleImgPath = (src) => {
return src.includes('star.png')
? '/images/starFilled.png'
: '/images/star.png';
};
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} />);
let favBtn = screen.getByRole('button');
const favBtn = screen.getByRole('button');
let expectedImgPath = toggleImgPath(favBtn.getAttribute('src'));
fireEvent.click(favBtn);

View File

@ -1,11 +1,15 @@
// React
import React from 'react';
// Bootstrap components
import ToggleButton from 'react-bootstrap/ToggleButton';
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';
// Custom components
import SearchFilterBar from './SearchFilterBar';
import Element from './Element';
import styles from '../../styles/List.module.css';
// React Bootstrap Components
import ToggleButton from 'react-bootstrap/ToggleButton';
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';
/**
*
* @param {*} list A list of either workouts or exercises
@ -23,7 +27,7 @@ export default function List({ list, listType, selected, setSelected }) {
<div className={styles.container}>
<SearchFilterBar />
{/* The list, A group of toggle buttons*/}
{/* The list. A group of toggle buttons, so that the active one can be kept track of */}
<div className={styles.scrollableContainer}>
<ToggleButtonGroup
type={listType}

View File

@ -1,3 +1,4 @@
// React
import React from 'react';
// Bootstrap components

View File

@ -1,29 +0,0 @@
// Next components
import Link from 'next/link';
// Bootstrap components
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
// Styles
import styles from '../../styles/Navbar.module.css';
function TopNavbar() {
return (
<Navbar className={styles.navbar} fixed="top">
<Link href="/" passHref>
<Nav.Link className={styles.title}>Workout Buddy</Nav.Link>
</Link>
<Nav fill className={styles.items}>
<Link href="/exercises" passHref>
<Nav.Link className={styles.item}>Exercises</Nav.Link>
</Link>
<Link href="/workouts" passHref>
<Nav.Link className={styles.item}>Workouts</Nav.Link>
</Link>
</Nav>
</Navbar>
);
}
export default TopNavbar;

View File

@ -0,0 +1,54 @@
// React
import React from 'react';
// Bootstrap Components
import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
// Next Components
import Link from 'next/link';
// Custom Components
import ProfileView from '../Profile/ProfileView';
import SignInView from '../Profile/SignInView';
// Styles
import styles from '../../styles/Navbar.module.css';
export default function TopNavbar() {
// TODO: Authentication
function profileSignIn(isSignedIn) {
if (isSignedIn === true) {
return <ProfileView />;
}
return <SignInView />;
}
return (
<Navbar className={styles.navbar} fixed="top" expand="sm" collapseOnSelect>
<Container fluid>
<Navbar.Brand className={styles.title} bsPrefix="no-styling">
My Workout Buddy
</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar" />
<Navbar.Collapse id="responsive-navbar">
<Nav className="me-auto">
<Link href="/exercises" passHref>
<Nav.Link className={styles.item}>Exercises</Nav.Link>
</Link>
<Link href="/workouts" passHref>
<Nav.Link className={styles.item}>Workouts</Nav.Link>
</Link>
</Nav>
<Nav>
<Nav.Link className={styles.item}>{profileSignIn(true)}</Nav.Link>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
);
}

View File

@ -0,0 +1,78 @@
// React
import React, { useState } from 'react';
// Bootstrap Components
import Nav from 'react-bootstrap/Nav';
import Figure from 'react-bootstrap/Figure';
import Offcanvas from 'react-bootstrap/Offcanvas';
// Next Components
import Link from 'next/link';
// Custom Components
import SettingsView from './SettingsView';
// Styles
import styles from '../../styles/Profile.module.css';
export default function ProfileView() {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Nav onClick={handleShow}>Profile</Nav>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Profile</Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
<div className={styles.container}>
<main className={styles.main}>
<h1>Alice Brown</h1>
<Figure>
<Figure.Image
width={200}
height={200}
alt="200x200"
src="profile-pic.jpg"
/>
</Figure>
<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>
<Nav.Link className={styles.card}>
<p>
<SettingsView />
</p>
</Nav.Link>
</div>
<div>
<Nav.Link className={styles.card} onClick={handleClose}>
<p>Sign out</p>
</Nav.Link>
</div>
</div>
</main>
</div>
</Offcanvas.Body>
</Offcanvas>
</>
);
}

View File

@ -0,0 +1,70 @@
// React
import React, { useState } from 'react';
// Bootstrap Components
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 { FloatingLabel } from 'react-bootstrap';
// Styles
import styles from '../../styles/Settings.module.css';
export default function SettingsView() {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Nav onClick={handleShow}>Settings</Nav>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Settings</Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
<div className={styles.container}>
<main className={styles.main}>
<Form>
<h3>
<Form.Label>Change Name</Form.Label>
</h3>
<div className={styles.item}>
<FloatingLabel label="First Name" className="mb-3">
<Form.Control placeholder="Alice" />
</FloatingLabel>
<FloatingLabel label="Surname" className="mb-3">
<Form.Control placeholder="Brown" />
</FloatingLabel>
</div>
<h3>
<Form.Label>Change Password</Form.Label>
</h3>
<div className={styles.item}>
<FloatingLabel label="Enter New Password" className="mb-3">
<Form.Control type="password" />
</FloatingLabel>
<FloatingLabel label="Re-enter New Password" className="mb-3">
<Form.Control type="password" />
</FloatingLabel>
</div>
<div className={styles.item}>
<Button variant="primary" type="submit" onClick={handleClose}>
Confirm
</Button>
</div>
</Form>
</main>
</div>
</Offcanvas.Body>
</Offcanvas>
</>
);
}

View File

@ -0,0 +1,69 @@
// React
import React, { useState } from 'react';
// Bootstrap Components
import Nav from 'react-bootstrap/Nav';
import Offcanvas from 'react-bootstrap/Offcanvas';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
// Styles
import styles from '../../styles/Settings.module.css';
export default function SignInView() {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Nav onClick={handleShow}>Sign in</Nav>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas.Header closeButton>
<Offcanvas.Title />
</Offcanvas.Header>
<Offcanvas.Body>
<div className={styles.container}>
<main className={styles.main}>
<Form>
<h1>
<Form.Label>Sign In</Form.Label>
</h1>
<div className={styles.grid}>
<div className={styles.item}>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" />
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="Password" />
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicCheckbox">
<Form.Check type="checkbox" label="Remember me" />
</Form.Group>
</div>
</div>
<div className={styles.buttons}>
<Button variant="primary" type="submit">
Sign In
</Button>
<div className={styles.signup}>
<Button variant="primary" type="submit">
Sign Up
</Button>
</div>
</div>
</Form>
</main>
</div>
</Offcanvas.Body>
</Offcanvas>
</>
);
}

View File

@ -0,0 +1,34 @@
// React
import React from 'react';
// Bootstrap Components
import Container from 'react-bootstrap/Container';
import Col from 'react-bootstrap/Col';
import Dropdown from 'react-bootstrap/Dropdown';
import DropdownButton from 'react-bootstrap/DropdownButton';
import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
// Styles
import styles from '../../styles/Search.module.css';
export default function SearchBar() {
return (
<Container fluid className={styles.container}>
<Row>
<Col xs>
<Form>
<Form.Control type="search" placeholder="Search" />
</Form>
</Col>
<Col xs>
<DropdownButton title="Filter" bsPrefix={styles.filter}>
<Dropdown.Item href="#/action-1">Item 1</Dropdown.Item>
<Dropdown.Item href="#/action-2">Item 2</Dropdown.Item>
<Dropdown.Item href="#/action-3">Item 3</Dropdown.Item>
</DropdownButton>
</Col>
</Row>
</Container>
);
}

13
components/YouTube.jsx Normal file
View File

@ -0,0 +1,13 @@
// 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"
/>
);
}

View File

@ -1,13 +1,21 @@
# Coding standards
The coding standards to follow are based on the MDN Web Docs. The styling guidelines for the following languages are available to read here:
As this project is mainly written in JS, JSX, and CSS, formatting and linting
is only covered for these languages.
- [HTML](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/HTML)
- [CSS](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/CSS)
- [JavaScript](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Writing_style_guide/Code_style_guide/JavaScript)
Note that React components are written in JSX. A seperate styling guide for writing JSX is found [here](https://airbnb.io/javascript/react/).
Linting is handled by ESLint. Part of the code style that ESLint adheres to can
be found on their [documentation](https://eslint.org/docs/latest/rules/). As
this project relies on React, the Airbnb JavaScript Style Guide
([available here](https://airbnb.io/javascript/)) is also used.
## Enforcing styling guidelines
Contributors are encouraged to use a formatting program such as [Prettier](https://prettier.io) to format code before committing it.
Styling guidelines are enforced through the use of a few tools:
- [ESLint](https://github.com/eslint/eslint) is used for linting.
- [Prettier](https://github.com/prettier/prettier) is used for formatting.
- [Husky](https://github.com/typicode/husky) and lint-staged are used to format and lint as pre-commit hooks.
Additionally, as part of the CI/CD pipeline, commits are automatically checked
for linting and formatting issues when pushed to this repository. Builds that
fail these checks are not deployed.

986
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,11 +9,13 @@
"lint": "next lint",
"prettier-check": "prettier --check .",
"prettier-fix": "prettier --write .",
"test": "jest --coverage"
"test": "jest --coverage",
"configure-husky": "npx husky install && npx husky add .husky/pre-commit \"npx --no-install lint-staged\""
},
"dependencies": {
"bootstrap": "^5.2.1",
"next": "12.3.0",
"prop-types": "^15.8.1",
"react": "18.2.0",
"react-aria": "^3.19.0",
"react-bootstrap": "^2.5.0",
@ -23,13 +25,30 @@
"@testing-library/dom": "^8.17.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"eslint": "8.23.1",
"eslint": "^8.24.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "12.3.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.1",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1"
},
"lint-staged": {
"**/*.{js,jsx}": [
"eslint . --fix",
"prettier --write ."
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

View File

@ -1,22 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import WorkoutsPage from '../pages/workouts/index';
describe('The List of buttons displaying Workouts or Exercises', () => {
it('Updates the selected button when another is clicked in the Radio List', () => {
render(<WorkoutsPage />);
let btns = screen.getAllByRole('radio');
btns.forEach((btn1) => {
fireEvent.click(btn1);
expect(btn1.checked).toBeTruthy();
btns.forEach((btn2) => {
if (btn1 != btn2) {
expect(btn2.checked).toBeFalsy();
}
});
});
});
});

View File

@ -0,0 +1,39 @@
// React
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
// Custom Components
import WorkoutsPage from '../pages/workouts/index';
describe('The List of buttons displaying Workouts or Exercises', () => {
it('Updates the selected button when another is clicked', () => {
render(<WorkoutsPage />);
const btns = screen.getAllByRole('radio');
btns.forEach((btn1) => {
fireEvent.click(btn1);
expect(btn1.checked).toBeTruthy();
btns.forEach((btn2) => {
if (btn1 !== btn2) {
expect(btn2.checked).toBeFalsy();
}
});
});
});
});
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

View File

@ -1,13 +1,16 @@
import 'bootstrap/dist/css/bootstrap.min.css';
import '../styles/globals.css';
/* eslint-disable react/jsx-props-no-spreading */
// React
import React from 'react';
import { SSRProvider } from 'react-aria';
function MyApp({ Component, pageProps }) {
// Styles
import 'bootstrap/dist/css/bootstrap.min.css';
import '../styles/globals.css';
export default function MyApp({ Component, pageProps }) {
return (
<SSRProvider>
<Component {...pageProps} />
</SSRProvider>
);
}
export default MyApp;

View File

@ -1,5 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' });
}

View File

@ -1,29 +0,0 @@
// Next.js components
import Link from 'next/link';
// Bootstrap components
import TopNavbar from '../../components/Navbar/Navbar';
// Styles
import styles from '../../styles/Exercises.module.css';
export default function ExercisesPage() {
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<h1>This is the exercises page!</h1>
<div className={styles.grid}>
<div className={styles.card}>
<Link href="/" className={styles.card}>
<p>Click here to go back to the home page.</p>
</Link>
</div>
</div>
</main>
</div>
</>
);
}

51
pages/exercises/index.jsx Normal file
View File

@ -0,0 +1,51 @@
/* eslint-disable react/jsx-props-no-spreading */
// React
import React, { useState } from 'react';
// Custom components
import List from '../../components/List/List';
import TopNavbar from '../../components/Navbar/Navbar';
// 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';
export default function ExercisesPage() {
// 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(
exerciseList[0].name
);
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<List list={exerciseList} {...selectState} />
</main>
</div>
</>
);
}

View File

@ -1,8 +1,11 @@
// Next.js components
// React
import React from 'react';
// Next Components
import Head from 'next/head';
import Link from 'next/link';
// Bootstrap components
// Custom Components
import TopNavbar from '../components/Navbar/Navbar';
// Styles
@ -41,12 +44,6 @@ export default function Home() {
<p>Explore our catalog of exercises</p>
</div>
</Link>
<Link href="/profile">
<div className={styles.card}>
<h2>Login &rarr;</h2>
</div>
</Link>
</div>
</main>
</div>

View File

@ -1,53 +0,0 @@
// Next.js components
import Link from 'next/link';
// Bootstrap components
import TopNavbar from '../../components/Navbar/Navbar';
// Styles
import styles from '../../styles/Profile.module.css';
export default function ProfilePage() {
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<h1>Alice Brown</h1>
<div className={styles.grid}>
<div className={styles.card}>
<Link href="/profile" className={styles.card}>
<p>Your favourites</p>
</Link>
</div>
<div className={styles.card}>
<Link href="/profile" className={styles.card}>
<p>Your workouts</p>
</Link>
</div>
<div className={styles.card}>
<Link href="/profile" className={styles.card}>
<p>Settings</p>
</Link>
</div>
<div className={styles.card}>
<Link href="/profile" className={styles.card}>
<p>Sign out</p>
</Link>
</div>
<div className={styles.card}>
<Link href="/" className={styles.card}>
<p>Click here to go back to the home page.</p>
</Link>
</div>
</div>
</main>
</div>
</>
);
}

View File

@ -1,62 +0,0 @@
// Next.js components
import Link from 'next/link';
// React
import React, { useState } from 'react';
// Bootstrap components
import TopNavbar from '../../components/Navbar/Navbar';
import List from '../../components/List/List';
// Styles
import styles from '../../styles/Workouts.module.css';
// Import the Workout class so that we can create a dummy set of workouts to render
import Workout from '../../public/classes/Workout';
// A dummy workout list so that we have data to render.
// Once the database is implemented this will not be necessary
const workout_list = [];
workout_list.push(
new Workout('Push Workout', ['Chest', 'Shoulder', 'Triceps'])
);
workout_list.push(new Workout('Pull Workout', ['Back', 'Biceps', 'Abs']));
workout_list.push(
new Workout('Legs Workout', ['Quadriceps', 'Hamstrings', 'Calves'])
);
workout_list.push(
new Workout('Upper Workout', ['Chest', 'Back', 'Shoulder', 'Triceps'])
);
workout_list.push(new Workout('Workout 1', ['Chest', 'Shoulder', 'Triceps']));
workout_list.push(new Workout('Workout 2', ['Back', 'Biceps', 'Abs']));
workout_list.push(
new Workout('Workout 3', ['Quadriceps', 'Hamstrings', 'Calves'])
);
workout_list.push(
new Workout('Workout 4', ['Chest', 'Back', 'Shoulder', 'Triceps'])
);
export default function WorkoutsPage() {
const selectState = {};
[selectState.selected, selectState.setSelected] = useState(
workout_list[0].name
);
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<List list={workout_list} listType="radio" {...selectState} />
<div className={styles.grid}>
<div className={styles.card}>
<Link href="/" className={styles.card}>
<p>Click here to go back to the home page.</p>
</Link>
</div>
</div>
</main>
</div>
</>
);
}

45
pages/workouts/index.jsx Normal file
View File

@ -0,0 +1,45 @@
/* eslint-disable react/jsx-props-no-spreading */
// React
import React, { useState } from 'react';
// Bootstrap Components
import TopNavbar from '../../components/Navbar/Navbar';
import List from '../../components/List/List';
// Styles
import styles from '../../styles/Workouts.module.css';
// Import the Workout class so that we can create a dummy set of workouts to render
import Workout from '../../public/classes/Workout';
export default function WorkoutsPage() {
// 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(
workoutList[0].name
);
return (
<>
<TopNavbar />
<div className={styles.container}>
<main className={styles.main}>
<List list={workoutList} listType="radio" {...selectState} />
</main>
</div>
</>
);
}

View File

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

BIN
public/hammer_curl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

BIN
public/profile-pic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

3
public/search.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@ -1,5 +1,6 @@
.container {
padding: 0 2rem;
margin: 50px;
}
.main {

View File

@ -0,0 +1,11 @@
.instructions {
border: 2px solid black;
border-radius: 10px;
padding: 0px 20px;
margin: 30px 0;
}
.line {
display: inline-block;
padding: 0.5em 0;
}

View File

@ -4,10 +4,8 @@
.title {
font-size: x-large;
text-align: center;
color: #eeeeee;
padding-left: 12px;
padding-right: 12px;
padding: 0 12px;
}
.items {
@ -29,11 +27,8 @@
font-size: x-large;
}
.title {
visibility: collapse;
}
.items {
margin: auto;
gap: 5vh;
}
}

View File

@ -3,7 +3,6 @@
}
.main {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;

30
styles/Search.module.css Normal file
View File

@ -0,0 +1,30 @@
.container {
padding-top: 10px;
padding-bottom: 10px;
}
.filter {
background: #fff;
color: black;
border: 2px solid lightgray;
border-radius: 3px;
padding: 5px 10px;
width: 100%;
}
.container input {
text-align: center;
background-image: url(../public/search.svg);
background-size: 8%;
background-position: 6vw 7px;
background-repeat: no-repeat;
text-indent: 20px;
}
.container ::-webkit-input-placeholder {
text-align: center;
}
.container :-moz-placeholder {
text-align: center;
}

140
styles/Settings.module.css Normal file
View File

@ -0,0 +1,140 @@
.container {
padding: 0 2rem;
}
.main {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;
flex-direction: column;
}
.footer {
display: flex;
flex: 1;
padding: 2rem 0;
border-top: 1px solid #eaeaea;
justify-content: center;
align-items: center;
}
.footer a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
margin: 4rem 0;
line-height: 1.5;
font-size: 1.5rem;
}
.code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
}
.card {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
max-width: 300px;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
margin-left: 0.5rem;
}
.buttons {
display: flex;
justify-content: center;
}
.signup {
margin-left: 4rem;
}
.item {
width: 100%;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}
@media (prefers-color-scheme: dark) {
.card,
.footer {
border-color: #222;
}
.code {
background: #111;
}
.logo img {
filter: invert(1);
}
}

View File

@ -1,4 +1,12 @@
html,
:root {
--pallete-blue: #1d93ab;
--pad-width: 1rem;
--card-radius: 10px;
--wave-height: 22vh;
--footer-padding: 1.5em;
}
body {
padding: 0;
margin: 0;
@ -20,7 +28,7 @@ a {
color-scheme: dark;
}
body {
color: white;
background: black;
color: black;
background: white;
}
} */