Merge main into hayz-checkbox-list
This commit is contained in:
commit
41219453f9
|
@ -1,3 +1,4 @@
|
|||
.next
|
||||
dist
|
||||
node_modules/
|
||||
.husky
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,5 +20,4 @@ jobs:
|
|||
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run prettier-check
|
||||
- run: npm run prettier-fix
|
||||
|
|
|
@ -37,3 +37,6 @@ next-env.d.ts
|
|||
|
||||
# development
|
||||
/.vscode
|
||||
|
||||
# linting
|
||||
/.husky
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install lint-staged
|
|
@ -4,3 +4,4 @@ node_modules/
|
|||
.vscode/
|
||||
.github/
|
||||
next.config.js
|
||||
coverage
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
|
@ -1,3 +1,4 @@
|
|||
// React
|
||||
import React from 'react';
|
||||
|
||||
// Bootstrap components
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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(),
|
||||
})),
|
||||
});
|
|
@ -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;
|
|
@ -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' });
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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 →</h2>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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.';
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 257 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
|
@ -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 |
|
@ -1,5 +1,6 @@
|
|||
.container {
|
||||
padding: 0 2rem;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
} */
|
||||
|
|
Loading…
Reference in New Issue