Workout Timer Active Page

Circuit Workout Timer

The Circuit Workout Timer web app is designed to help users track their work and rest periods during circuit training. The app allows users to create customizable workouts with exercises, rests, and sets. Users can track their current and upcoming exercise during the workout and monitor total workout time and remaining time.

Skip to

Live Link

U: guest P: guest

Inspiration

I was inspired to create this app during the pandemic lockdowns. At the time, I was laid off, stuck at home, and needed a way to stay mentally and physically healthy, so I took up kettle bell training. Circuit training is a popular way to train with kettle bells. A circuit can consist of 5 to 8 exercises, each performed for 30 to 90 seconds, one after the other. After completing a circuit, you take a 60 to 90-second break and then repeat the circuit 3 or more times. The challenge with circuit training is keeping track of time and exercise names and orders.

workout timer dashboard
Dashboard list your saved workouts. Here users can create, edit and delete your workouts.

Features

Customizable Workouts: Users can create workouts with exercises, rests, and sets tailored to their fitness goals.

Real-Time Tracking: The app allows users to track their current and upcoming activities during the workout. The app displays the remaining time for the current exercise and the remaining time for the entire workout.

Cloud Storage: Users can save their workouts in the cloud by creating a profile; This makes it easy to access their workouts from any device.

Secure Authentication: The app uses NextAuth.js to authenticate users; This ensures that only authorized users have access to their workout data.

Database Integration: User data is stored in a PostgreSQL database hosted on www.railway.app. This allows for secure and reliable storage of user data.

Voice Prompts: The app uses Web Speech API to prompt the user about the current and next exercise.

workout timer active state
Timer showing total workout time, the current set, and current exercise time.

Stack

I based my technology choices on their flexibility and ability to work efficiently together.

Typescript: For type safety and makes working with React fun by helping avoid runtime errors like passing the wrong data via props.

Next.js: For its easy-to-use API layer, which allows custom API routes to be defined, making CRUD operations and handling front-end and back-end interactions simple.

Prisma ORM: Its integration with Next.js and type-safe, intuitive, client-side API.

React Query: For fetching and mutating data on the client side, I opted for React Query, which offers data caching features that prevent over-fetching, leading to faster load times.

NextAuth: I decided on AOuth to avoid storing user credentials in the database for security reasons. Also, NextAuth is easy to integrate into a Next.js and Prisma ORM stack with minimal configuration.

Tailwind CSS: I appreciated the utility-first approach of Tailwind CSS and its built-in utilities for colors, spacing, and media queries, making it feel like a design system that was ready to use with only minor adjustments required for customization.

Radix-UI: I took accessibility seriously, so I chose Radix UI, a collection of un-styled, composable, and accessible UI components that allowed me to build modern web applications with essential components like forms, dropdowns, and alert dialogs.

workout timer paused state
Paused state. User's can still see current workout name and exercise

What I've Learned

Throughout this project, I gained a wealth of knowledge I couldn't possibly share in one sitting; here are the most important things I learned.

TypeScript: I learned how to build type-safe, reusable functions with generics. Use built-in utility types such as Record, Partial, Pick, and Omit; how to use utility libraries such as ts-toolbelt, which offers more comprehensive utility options.

UI Components: I learned how to implement accessible and reusable mobile-first UI components with Radix UI, an unstyled, accessible components library.

RESTful API: I learned how to develop RESTful API endpoints utilizing Next.js and Prisma ORM to allow CRUD operations.

Server Side: I learned about the Request and Response cycle, serverless functions, HTTP request methods (e.g., GET, POST), and how to handle server response status codes indicating the outcome of the request (e.g., 200 for a successful response, 404 for a not found error).

Database Schema: I learned how to design a database schema to enable many-to-many associations between users, workouts, and exercises; and how to use the "CASCADE" option to maintain and enforce the relationships between tables.

Authentication: I learned how to implement user authentication via JSON Web Token (JWT) session cookies and how to secure routes with Next.js middleware.

workout edit view
Edit workout. Users can rename, add sets, add/delete activity to their workout.

Challenges

Managing state and reducing unnecessary re-renders: The core of the app is the timer functionality. It is driven by a custom hook that executes a setInterval method and drives the counters, the circular graph, and the order of the exercises. I had to be careful where I placed this setInterval Hook to not re-render components that did not need to be updated on every render. Not everything in the UI required to re-render along with the timer, and I only relied on memoization techniques (useMemo, useCallBack) when necessary and as a last resort.

Reducing database queries to a minimum: A drag-and-drop feature in the edit workout screen allows users to reorder their exercises. For a better user experience, I decided the order would be instantly saved in the DB whenever the user changed the exercise order, thus avoiding losing your work in case of any system error or crash. I had a few solutions in mind:

1. On the client side, loop through every exercise and mutate each exercise's display order value; This was slow and would not scale well for workouts with many activities.

2. Same as option 1, but the loop is done on the server side. Also, slow and any fallback solution in the case of server errors took a lot of work.

3. I researched and found that Prisma ORM has a feature called Transactions. Transactions in Prisma ORM help you group multiple database operations into a single unit of work to succeed or fail, ensuring that your data remains consistent and falls back to its original state in case of failure.

I went with Option 3 because it offered the least friction with implementation and also offered a gentle fall back if the query failed.

add workout module
Add an activity. Give it a name and a duration.

What's next?

The next challenge is to port it into a native mobile application using Expo and React Native; This will provide users with even more convenience and accessibility. Plans also include adding new features, such as integration with wearable devices and more personalized workout recommendations with AI assistance.