Transforming the Fitness App Experience using React JS
Client side - React js
The application was born out of my personal challenges in maintaining a regular workout routine. I often found it difficult to keep track of my exercises and stay committed on a daily basis. This inconsistency was largely due to the absence of a structured system that could help me monitor my progress and motivate me to stick to my fitness goals. This application aims to address these issues by offering features that promote accountability and consistency, ultimately making it easier for users like myself to achieve their health and fitness objectives. Here is the link to the live site https://fitness-track-liard.vercel.app/
Signup.jsx and Signin.jsx components
These components help with user authentication to improve privacy and also keep records of all user information for easy management and access. The user details are stored in the Redux store using the useDispatch
function.
The signup page accepts the user's name, email, and password, sends this information to the backend server, and saves the fitness track token in localStorage. Each token generated is unique for each user and helps improve data integrity. useDispatch
is a hook from the Redux library, specifically from react-redux
, used in functional React components to dispatch actions to the Redux store. When you call useDispatch
, it gives you access to the dispatch
function, which can be used to send actions that update the state in the Redux store.
import { useState } from "react"
import {UserSignUp} from '../api/index'
import { useDispatch } from "react-redux";
import { loginSuccess } from "../redux/userSlice";
const Signup = () => {
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const [buttonDisabled, setButtonDisabled] = useState(false);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const validateInputs = () => {
if (!name || !email || !password) {
alert("Please fill in all fields");
return false;
}
return true;
};
const handelSignUp = async (e) => {
e.preventDefault();
setLoading(true);
setButtonDisabled(true);
if (validateInputs()) {
await UserSignUp({ name, email, password })
.then((res) => {
dispatch(loginSuccess(res.data));
alert("Account Created Success");
setLoading(false);
setButtonDisabled(false);
})
.catch((err) => {
alert(err.response.data.message);
setLoading(false);
setButtonDisabled(false);
});
}
};
return (
<div className=" flex flex-col mb-4 gap-10" >
<div>
<h1 className="uppercase text-[50px]">Welcome to gym-fit</h1>
<span className="text-[30px]">Enter to you details below</span>
</div>
<form
onSubmit={handelSignUp()}
className=" flex flex-col">
<label >
<span>Email</span>
</label>
<input placeholder="Email"
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className=" bg-slate-500 xl:h-[60px] h-[40px] xl:w-[30vw] w-[310px] text-[14px] xl:text-[16px] rounded-[10px] outline-none border-none"/>
<label >
<span>Username</span>
</label>
<input placeholder="Username"
name="name"
value={name}
onChange={(e) => setName(e.target.value)}
type="text"
className=" bg-slate-500 xl:h-[60px] h-[40px] xl:w-[30vw] w-[310px] text-[14px] xl:text-[16px] rounded-[10px] outline-none border-none"/>
<label>
<span>Password</span>
</label>
<input placeholder="Password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
className=" bg-slate-500 xl:h-[60px] h-[40px] xl:w-[30vw] w-[310px] text-[14px] xl:text-[16px] rounded-[10px] outline-none"/>
<button
type="submit"
className="p-3 mt-2 xl:w-[10vw] w-[100px] rounded-[10px] bg-pink-500" >Sign up</button>
</form>
</div>
)
}
export default Signup
Dashboard.jsx Component
The dashboard component serves as the application's main page, providing a comprehensive overview of workout records through pie charts and bar charts, detailing daily and weekly activities.
The dashboardData, getTodaysWorkout and addNewWorkout functions call the API actions using Axios
, which acts as a bridge between my backend and frontend, thus updating the state for data
and todaysWorkouts
. The frontend connects to my render backend application using the baseURL.
import axios from "axios";
const API = axios.create({
baseURL: "https://fitness-app-8kfe.onrender.com",
});
export const UserSignUp = async (data) => API.post("/signup", data);
export const UserSignIn = async (data) => API.post("/signin", data);
export const getDashboardDetails = async (token) =>
API.get("/dashboard", {
headers: { Authorization: `Bearer ${token}` },
});
export const getWorkouts = async (token, date) =>
await API.get(`/workout${date}`, {
headers: { Authorization: `Bearer ${token}` },
});
export const addWorkout = async (token, data) =>
await API.post(`/workout`, data, {
headers: { Authorization: `Bearer ${token}` },
});
The dashboard includes a text area designed to accept workouts in a specified format, which then generates a gymroutine
card displaying all daily workouts. Additionally, a logout button is located at the top right; clicking it removes the fitnesstrack token
from localStorage and redirects the user back to the signup page for entering user details.
import { useEffect, useState } from "react";
import { counts } from "../Assets/data"
import CardCount from "../Components/CardCount"
import GymRoutine from "../Components/GymRoutine";
import SetPieChart from "../Components/SetPieChart";
import WeeklyStats from "../Components/WeeklyStats";
import { addWorkout, getDashboardDetails, getWorkouts } from "../api";
const Dashboard = () => {
const [loading , setLoading] = useState(false);
const [buttonLoadng,setButtonLoading] = useState(false);
const [data , setData] = useState();
const [todayWorkouts, setTodaysWorkout] = useState([]);
const [workout, setWorkout] = useState(`
#Legs
Back Squat
5 sets X 15 reps
30 kg
10 min`);
const dashboardData = async() =>{
setLoading(true);
const token = localStorage.getItem("fittrack-app-token")
await getDashboardDetails(token).then((res)=>{
setData(res.data);
console.log(res.data);
setLoading(false);
});
};
const getTodaysWorkout = async() =>{
setLoading(true);
const token = localStorage.getItem("fittrack-app-token");
await getWorkouts(token,"").then((res)=>{
setTodaysWorkout(res?.data?.todayWorkouts);
console.log(res.data);
setLoading(false);
});
};
const addNewWorkout = async() =>{
setButtonLoading(true);
const token = localStorage.getItem("fittrack-app-token");
await addWorkout(token,{workoutString:workout})
.then((res)=>{
setWorkout(res?.data?.workout);
dashboardData();
getTodaysWorkout();
setButtonLoading(false);
})
.catch((err)=>{
alert(err);
})
}
useEffect(()=>{
dashboardData();
getTodaysWorkout();
},[]);
return (
<div className="h-[100%] flex flex-[1] bg-slate-800 text-white py-[22px] overflow-y-scroll">
<div className="flex-[1] max-w-[1400px] flex flex-col xl:gap-5 gap-3">
<h1 className="text-[30px] text-tertiary font-bold p-3">Dashboard</h1>
<div className="flex flex-wrap justify-between py-5 mt-5 xl:gap-5 gap-3">
{counts.map((item,index)=>(
<CardCount key={index} data={data} item={item}/>
))}
</div>
<div className="flex flex-wrap justify-between py-5 mt-5 xl:gap-5 gap-3">
<WeeklyStats data={data}/>
<SetPieChart data={data}/>
<div className="flex flex-[1] flex-col min-w-[280px] xl:p-6 p-4 border-[1px] rounded-[10px] border-tertiary gap-2">
<h2 className="font-semibold text-[16px] text-tertiary">Add New Workout</h2>
<textarea
rows={5}
value={workout}
onChange={(e)=>setWorkout(e.target.value)}
placeholder="Enter in this order"
className=" border-none text-[16px] p-8 bg-slate-400 rounded-[10px] text-black"
/>
<button
onClick={()=>addNewWorkout()}
className="bg-tertiary px-8 py-4 border-none rounded-[10px]"> SEND</button>
</div>
<div className="flex flex-col justify-between xl:gap-5 py-4 gap-3">
<h2 className="uppercase font-bold text-[24px] text-tertiary">Today's Workout</h2>
<div className="flex flex-wrap justify-center xl:gap-5 mb-[100px] gap-3">
{todayWorkouts.map((workout,index)=>(
<GymRoutine workout={workout} key={index}/>
))}
</div>
</div>
</div>
</div>
</div>
)
}
export default Dashboard
I prefered using the mui-x-charts
library to create my pie-chart and bar-chart since they are simple to use and visually appealing. To plot a pie chart, a series must have a data property containing an array of objects. Those objects should contain a property value
. They can also have a label
property. I coded my data in the backend for easier management and quicker updates.
import { PieChart } from "@mui/x-charts"
const SetPieChart= ({data}) => {
return (
<div className="flex flex-[1] flex-wrap flex-col border-[1px] bg-slate-500 border-slate-400 rounded-[10px] shadow-lg">
<h2 className="text-[24px] font-bold uppercase text-tertiary">Set pattern</h2>
{data?.pieChartData && <PieChart
series={[
{data:data?.pieChartData,
paddingAngle:4,
innerRadius:30,
outerRadius:150,
cornerRadius:5
}
]}
height={300}
/>}
</div>
)
}
export default SetPieChart
Bar charts series should contain a data
property containing an array of values. You can customize bar ticks with the xAxis
. This axis might have scaleType='band'
and its data
should have the same length as your series. The series
defines the actual data to be plotted and represents the value on each bar. The height
is the chart height in pixels.
import { BarChart } from "@mui/x-charts/BarChart"
const WeeklyStats = ({data}) => {
return (
<div className="flex flex-[1] flex-wrap h-[fit] flex-col border-[1px] bg-slate-500 border-slate-400 p-6 rounded-[10px] shadow-lg">
<h2 className="text-[24px] font-bold uppercase text-tertiary">Weekly Statistics</h2>
{data?.totalWeeksCaloriesBurnt && <BarChart xAxis={[
{scaleType:"band",
data: data?.totalWeeksCaloriesBurnt?.weeks
}
]}
series={[{data:data?.totalWeeksCaloriesBurnt?.caloriesBurnt}]}
height={300}
className="text-tertiary"
/>}
</div>
)
}
export default WeeklyStats
Workouts.jsx Component
The <LocalizationProvider>
and <DateCalendar>
components from MUI are utilized to display a calendar with specific localization and functionality. The <LocalizationProvider>
component offers localization support for date and time pickers by setting a date adapter, in this case, AdapterDayjs
. This adapter integrates the Day.js date library with MUI components, allowing for date formatting, parsing, and handling based on Day.js capabilities.
The <DateCalendar>
component serves as the actual calendar view, displaying dates and enabling user interaction. It is used for selecting dates and can trigger events when a date is chosen.
import GymRoutine from "../Components/GymRoutine"
import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
import {DateCalendar} from "@mui/x-date-pickers/DateCalendar"
import { useState,useEffect } from "react"
import { useDispatch } from "react-redux"
import { getWorkouts } from "../api"
const Workouts = () => {
const dispatch = useDispatch();
const [todayWorkouts, setTodaysWorkouts] = useState([]);
const [loading, setLoading] = useState(false);
const [date, setDate] = useState("");
const getTodaysWorkout = async () => {
setLoading(true);
const token = localStorage.getItem("fittrack-app-token");
await getWorkouts(token, date ? `?date=${date}` : "").then((res) => {
setTodaysWorkouts(res?.data?.todayWorkouts);
console.log(res.data);
setLoading(false);
});
};
useEffect(() => {
getTodaysWorkout();
}, [date]);
return (
<div className="h-full flex items-center bg-slate-800 flex-wrap gap-10 text-white py-4 px-5">
<div className="flex flex-[1] h-[400px] w-[350px] flex-wrap bg-slate-500">
<h1 className="font-bold text-[20px] p-5 text-tertiary">Pick a date</h1>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateCalendar
onChange={(e)=>setDate(`${e.$M + 1}/${e.$D}/${e.$y}`)}
className="text-tertiary uppercase"/>
</LocalizationProvider>
</div>
<div className="flex flex-[1] gap-4 flex-wrap">
{todayWorkouts.map((workout,index)=>(
<GymRoutine workout={workout} key={index}/>
))}
</div>
</div>
)
}
export default Workouts
Contact.jsx Component
I used the emailjs
library to send emails using only client-side technologies. No server is needed—just connect EmailJS to a supported email service, create an email template, and use one of our SDK libraries to send an email. Click the link above to start creating your own email templates in EmailJS.
I used the useRef()
hook to keep the form values that need to stay the same between renders. Since it doesn't cause re-renders, it's useful for storing the user's information.
import { useRef } from 'react'
import emailjs from '@emailjs/browser';
import cover from '../Assets/gymLarge.jpeg'
const Contact = () => {
const form = useRef();
const sendEmail = (e) => {
e.preventDefault();
emailjs
.sendForm('service_96b50mg', 'template_o7k8wg3', form.current, {
publicKey: 'j5lxyQK3Pt3mDexe0',
})
.then(
() => {
alert('SUCCESS!');
},
(error) => {
alert('FAILED...', error.text);
},
);
};
return (
<div className="flex h-fit items-center gap-4 bg-slate-800 flex-wrap text-white text-[20px]">
<div
className="flex flex-wrap sm:items-center px-6 xl:items-end justify-center flex-col gap-2 flex-[1]">
<h1 className=" uppercase text-[25px] text-tertiary font-bold xl:pr-20 ">Get in touch</h1>
<p className="text-[20px] font-extrabold flex ">Please enter your feedback below</p>
<form
ref={form}
onSubmit={sendEmail}
className="flex flex-col flex-wrap gap-3 ">
<label className="flex flex-col font-bold" >
Your name
<input type="text"
name='user_name'
className="px-3 py-6 xl:w-[550px] sm:w-[360px] bg-slate-400 font-normal border-none rounded-[15px] outline-none"/>
</label>
<label className="flex flex-col font-bold">
Your Email
<input type="text"
name='user_email'
className="px-3 py-6 xl:w-[550px] sm:w-[360px] bg-slate-400 font-normal text-black border-none rounded-[15px] outline-none"/>
</label>
<label className='flex flex-col font-bold'>
Your Message
<textarea
name='message'
rows={5}
className="p-10 border-none outline-none bg-slate-400 rounded-[15px] "
/>
</label>
<button
className="p-5 border-none rounded-[15px] min-w-[360px] max-w-[550px] bg-tertiary outline-none mb-3"
>
Send</button>
</form>
</div>
<div className="flex flex-wrap flex-[1]">
<div className='flex flex-col gap-3'>
<h1 className='text-[25px] font-black text-tertiary uppercase '>Thank you for your feedback</h1>
<img src={cover} alt=""
className='h-[500px] w-[600px] object-cover rounded-[10px]'
/>
</div>
</div>
</div>
)
}
export default Contact
Subscribe to my newsletter
Read articles from Brian Muchira directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by