I was looking for a project to build in order to practice using web app frameworks. React and Laravel. Rather than building a to-do list or a tic-tac-toe game, I was able to find a project that solved a real-world problem and had real design constraints.
Allbiz Supplies (my long-time employer and client) was experiencing some pain when it came to the process of recording hours worked by employees. My manager (the business owner) asked me if I had any ideas, and I offered to take this project on in my own time as a chance to study.
Even though this was just a side project, I wanted to go through the proper process of creating a spec, follow a systematic coding process and do thorough user acceptance testing.
Before starting the project, I made some assumptions about the architecture for the app:
- It would be a web app. I would use Laravel to build the backend, I would use React with Redux to build the frontend, because these were the frameworks I wanted to study.
- It would be hosted alongside the company’s website on a LAMP server, incurring no extra running costs.
I decided to take a linear approach to the project, as opposed to a cyclical, Agile methodology. I didn’t intend to show the app to Allbiz until all of the features were made and the app was ready to test in full, and I couldn’t commit to a regular weekly development cycle, as this project had to be put aside whenever I needed to do work for paying clients.
The app needed to solve the following problems:
- The business needed all employees to submit their timesheets electronically, but at least one staff member had relatively little experience with computers.
- The existing timesheet system required staff to fill out a form PDF, but the form wasn’t able to validate all of the information that employees entered (e.g. “do I need to tick Holiday if I’m on leave, or if it’s a public holiday, or both of those times?”).
- If employees reused their timesheet PDF from the previous week (and just changed the dates) then it was possible to forget to clear out the comment field, which might say “Pay me Long Service Leave” (this would cause confusion for payroll, at the very least).
- Some staff occasionally forgot to submit their timesheets (sometimes, that employee was me).
- Personally, as an employee I wanted the timesheet to be faster to submit, and I wanted to be able to do it using my phone. The existing system required me to get to a computer that had a blank copy of the PDF timesheet.
There were some problems that the app wouldn’t try to solve:
- It wouldn’t attempt to integrate with payroll software. The manager and bookkeeper would still have to manually transcribe employee’s hours from the timesheet to the payroll system.
- Employees would still be limited to entering one shift per day, and the timesheet week would always start on Monday.
I created an spec using Joel Spolsky’s guide to functional specifications. Before designing any views or writing any logic, I wrote two relatively thorough user scenarios: one describing my coworker who lacked computer experience, and one for myself. I reasoned that if the app satisfied both of us then it would most likely satisfy the rest of the staff.
Before starting the spec I had a brief meeting with my manager to scope out the app’s requirements. I had already collected most of the requirements for the app during various discussions with my manager at work and this meeting just served as an opportunity to make sure the app recorded everything it needed to, and nothing more.
Besides addressing the pain points listed above, the design of the app was constrained by several factors:
- It had to be a web app, because that was what I wanted to practice. This ruled out making a native Windows app or a native mobile app.
- The one frontend had to work on both desktop and mobile devices.
- It had to deliver only the bare minimum of required features, as I was building the app on my own time and only in my spare time, and with a technology stack that was relatively new to me.
- It needed to be inexpensive to maintain, preferably piggybacking on the existing website’s server.
- The manager and bookkeeper’s payroll process needed to remain the same, which meant that the app still needed to send PDF timesheets by email.
Feature: Simplify the timesheet submission process
Firstly, I needed to simplify the process of submitting a timesheet so that even the least computer-savvy employee could do it without needing help. The problem with the existing system was that you had to do all of these steps:
- Find and open the empty PDF file.
- Fill it out.
- Save a copy somewhere.
- Open the email client.
- Create a new message to payroll.
- Attach the timesheet PDF.
- Send the email.
While the business could have invested a huge amount of time teaching the employees how to use this system without assistance, I saw this project as an opportunity to practice some Human-Centred Design and build an app that meets user’s needs, rather than the other way around.
To make timesheet submission simpler, I decided to automate the email process and auto-fill as much of the timesheet as possible. As soon as the user clicked Submit on the timesheet form, the timesheet would be immediately emailed to payroll in HTML and PDF, and CC’d to the employee. The timesheets would also be stored in the app’s database, so the employee could view their past timesheets simply by accessing an index of timesheets in the app (this index is the home page for a logged-in user).
This design also meant that the manager didn’t actually need to interact with the app at any time; she would just receive a flurry of emails the day before payday, which is the same workflow as the existing system. The emails would continue to have PDF attachments, but these PDFs would be programmatically generated by the app, and would be reproduced in the email message itself in nicely-formatted HTML.
Feature: Prevent user mistakes
Secondly, I needed to validate the employee’s input to reduce the possibility of making a mistake. I decided that the app would force the user to un-check any shift that they didn’t work, and require them to select a reason for each day without a shift, so the manager would see which days were rostered days off, which were leave and which were unplanned absences. The app would alert the user if they entered a invalid shift times or left a day empty. This wouldn’t prevent a careless user from simply entering false times or incorrect reasons, but it would catch some of the more blatant errors.
Feature: Increase convenience
Thirdly, I was personally motivated to remove repetitive data entry. I hate repeating myself. Repeating myself is a waste of time! So I specified a feature that allowed users to save their current form values as their default shifts so that the shifts would be pre-populated the next time. The shift dates would be added automatically as well. The timesheet form would just provide a week selector that lets the user select the timesheet week.
Once the user had saved their shifts, submitting a timesheet was as simple as:
- Open the app.
- Log in.
- Click Create New Timesheet.
- Change the shift times (if different from the defaults) or the selected week (if not the current week).
- Add a comment if needed (this would always be blank by default).
- Click Submit.
- Log out (if using a shared workstation).
Feature: send reminders to forgetful staff
Lastly, I needed to help people not forget to do their timesheet. This feature didn’t make it into the original spec – I only added it after the manager specifically asked if there was a way to include it, because late/missing timesheets were a nuisance.
The business already subscribed to an online SMS provider, so I simply specified that the app should send an automated SMS to users if they hadn’t submitted a timesheet within a few hours of the deadline on the day before pay day.
Limit access to authorised employees
The app needed to be able to identify the current user and needed to prevent unauthorised usage, so I specified that each employee would need to login with their email and a password. I also designed an admin interface so the app admin (me) could create new users and automatically send new users a welcome email with a “Set Password” link. User passwords would be entirely secret – as admin I could neither read or write a user’s password.
The app would also present users with a logout button immediately after they submitted their timesheet, to remind them to log out of the app on shared computers.
The app also had to be fully usable on a phone. This made the form design more difficult but the convenience of using the app on a phone was too much to overlook.
Reviewing the spec
Once the spec was done, I presented it to my manager, and we went through a few revisions until it met her reporting requirements. In particular, the app needed to record every absence and report a reason for each so that the business could pay entitlements when owed. The existing timesheet PDF didn’t record all of this information, so it took me a couple of minor revisions before the spec correctly described the feature the manager wanted.
Once my manager agreed that the specification was satisfactory, I began the coding stage.
As with previous projects, I created a Docker Compose file that provisioned Apache httpd, MariaDB, PHP-FPM, PHP CLI and phpMyAdmin containers. Docker allowed me to serve a test site locally and use Laravel Artisan without polluting my host environment with project-specific software. Composer and Node were installed on my host machine in order to install Laravel and use Create React App.
I used Kanban boards on Jira to keep track of my tasks, implementing one feature or fixing one bug at a time.
I used Git for version control and deployment and as much as practicable I made atomised commits (each commit resolved one task or bug). Partway through the project I decided to split the project into two repositories, but managed to split the repo using
git-filter-repo. Several times during the project I needed to restore my code to the last commit as I made mistakes while implementing features.
The Laravel API
I opted for a MySQL database and a JSON:API interface. I opted for a JSON:API because it would allow me to read and write timesheet and user records, and I could use the existing Laravel JSON:API library. It also saved me from needing to design and implement my own REST API.
I opted for session-based authentication using Laravel Sanctum and wrote my own controllers for the auth endpoints, based on the Laravel Breeze starter kit. I also hardened the app against attack by implementing a strict CORS policy.
I also wrote an endpoint to handle a state transition that couldn’t be handled nicely with JSON:API, so the final API was a mix of JSON:API and a free-form REST API.
Building the backend allowed me to use a few different features offered by Laravel, including:
- Laravel’s authentication and authorisation systems
- Eloquent ORM
- Custom console commands
- Integrating with an SMS service and SMTP mailserver using Notifications and Channels
- Events and subscribers to trigger notifications
- Laravel’s built-in test fixtures and helpers
- Markdown Blade templates
While Laravel’s documentation is excellent, I relied on XDebug to step through the code while I was debugging failing tests or trying to understand how to implement some features, such as my auth logic.
The React/Redux frontend
I started the frontend code using Create React App’s TypeScript template.
I took inspiration from the component architecture I’d seen in Gatsby: The top level
App.tsx component contained only an initialisation effect and a router, using React Router. Each route just served a single
These page components acted like MVC controllers: they accessed the Redux store built the view and executed any logic related to updating the app state, such as a form submission.
I used Redux Toolkit to create store slices, keeping the logic for each model (users, timesheets, settings) separate. Each slice included asynchronous thunk actions that made calls to the server to fetch or persist data.
Some routes could only be accessed if the user was authorised, while others could only be accessed if the user was not authenticated at all.
AdminRoute components that wrapped around React Router’s
Route component: these routes checked the auth context and either redirected the user or simply denied access.
I implemented an
AuthContext provider instead of putting auth in the redux store, because the auth state was relatively simple, and the context API was easier to mock in tests. This also meant the auth logic was reusable in other projects even if they don’t use Redux.
axios to make HTTP calls. I explored some JSON:API libraries but ultimately found it easier to write my own adaptors to convert Redux state objects to JSON:API resources and vice versa.
With the exception of the timesheet entry form itself, most of the apps forms were very basic, so I created two hooks for reusable form logic:
useFormcontained validation and form state and was used by form components.
useFormControllerhandled form submission, and was used by page components.
one to hold reusable form state, validation and handlers, and another to do the same for the page component, which acts as a form controller.
I used Bootstrap and React Bootstrap to speed up development, and found that it was easier to use separate Sass files instead of CSS-in-JS as I had very little custom CSS. (It was OK for this app to have that stock Bootstrap look.)
I created a Messages context and hook to display flash messages to the user that automatically expired or could be manually dismissed. Once again I used the Context API instead of Redux, as the messages state was simple.
I also used this project as an opportunity to practice TDD. In particular I focused on following a cyclical “red, green, refactor” process and committed code after each cycle. Initially my RGR process was haphazard but I became more disciplined (and more productive) as I progressed. Towards the end of the project I also started to conform to the rules of MC-FIRE, in particular the practice of writing expressive tests that double as a kind of documentation.
For the frontend I used Jest (Create React App decided that for me) and Testing Library to write tests. For the backend I used Artisan, PHPUnit and Laravel’s built-in test helpers. I also added debugger configurations for XDebug and Node to my IDE (VS Code) so I could step through failing tests.
Since this web app was for internal use only, I decided to deploy the site to the production server and do user acceptance testing while it was live.
To deploy the backend I cloned the Git repo to the server, ran
composer install and migrated the database.
To deploy the frontend, I created a production build and used
scp to push that build to the server.
User acceptance testing
Over the course of several weeks, my coworker (who was the subject of my user scenario) used the timesheet app while I observed and made changes to the app’s functionality. My coworker was also extremely helpful in asking questions and reporting every obstacle he faced.
It turned out that my design and implementation has overlooked a number of required features, and this testing also uncovered several bugs that I hadn’t discovered by myself:
- My manager and I had both overlooked the need to include “Public Holiday” as a possible reason for not working a shift.
- The “save default shifts” button looked too similar to the submit button.
- The timesheet form needed a reset button.
For several weeks I switched to a cyclical development process, where my coworker and I would test each new iteration until there were no more problems to find. The upside of this approach was that it did not consume much more of my coworker’s time than was required to send his timesheet.
Now that the app is live, Allbiz employees are routinely using the app to submit their timesheets.
My manager is receiving more detailed and accurate information on our timesheets, and the HTML format makes the timesheets easier to check directly in her mail client.
I was able to streamline a tedious and tricky process. The project served as a challenging study project that gave me valuable practice using React, Redux, TypeScript, Laravel, JSON:API, Jest, PHPUnit and XDebug, and an assortment of software design patterns.
View the source code on Github
The front end. built with React:
The back end, built with Laravel: