Featured Projects

World Clock App

Tech Stack:

React

TypeScript

Next.js 14

Next.js API Routes (API Backend)

styled-components

Get current time and weather data for any location in the world!

The default location is based on the request IP address, and the location can be changed via a location search, powered by the GeoDB API.

Project Purpose and Goal

I wanted to create a React app from a design file to practice implementing a full design system with design tokens. I had previously created several marketing webpages based on Figma designs, but these were not interactive or dynamic.

I decided to use the Clock App Figma file from FrontendMentor.io. The designs included the layouts for mobile, tablet, and desktop sites.

My final product went beyond the original design by allowing location changes via the GeoDB Cities API for searching new locales, and Weather API for updated timezone and weather details based on the chosen location.

Main Challenge:

Integrating the GeoDB Cities API

When looking for an API for the city location search, I found the GeoDB API that was ideal in its functionality, but lacking in two main areas:

1. Poor Documentation

The documentation for the API was not very clear or complete. The best way for me to understand how it worked was to look at the API author’s sample Angular app, whose code was available on GitHub.

Solution

I used ChatGPT to decipher and translate much of that sample Angular code into what eventually became my Location Search component.

2. Unsecured Endpoint Throwing a Mixed Content Warning

I could not access the API through my app’s front end due to the endpoint being unsecured.

Solution

Next.js v13+ API routes allowed me to set up a convenient back end route that could access the endpoint and securely pass data to my front end code.

The setup was similar to my previous experience setting up an Express.js backend for my translation app, but more convenient as it is co-located within the Next.js project.

Lessons Learned

Following Design Files Removes Decision Friction and Improves Execution Speed

Starting this project from a Figma design made the process much smoother and quicker to get an MVP up and running. Once the basic designed components were in place, I was able to iterate and modify according to my own tastes, such as adding transitions and experimenting with new APIs used to populate the app’s data - aspects which were not included in the starting designs.

Screenshot of Multi-translate App

Multi-Language Translation App

Tech Stack:

React

TypeScript

Next.js 13

styled-components

Express.js (API Backend)

Translate text from one language into any number of other languages!

Powered by the Google Translate API, you can select multiple destination languages for simultaneous translation.

Project Purpose and Goal

This was one of my earliest attempts at a completely solo React app (i.e. no designs or structured guidance whatsoever), and my goal was to build an app that served a personal interest, but which I had not seen in another app.

I thought of the idea while traveling in South America in 2022-23.

In an effort to learn Portuguese while reinforcing my knowledge of Spanish, whenever I would look up an English word or phrase in one language, I would subsequently translate it into the other.

An app like this seemed useful to me as a learner to see the Spanish and Portuguese side by side, and would save me the additional step of translating into the second language in a separate transaction.

Main Challenge: Handling Concurrent API Calls

This app uses Google Translate’s API via a third-party endpoint on RapidAPI (I used this API because it has a free tier, whereas the official Google Translate API is never free for developers).

The API endpoint I’m using does not support batch calls or multiple input strings, so each request must be an independent API call.

Solution: A Single State Var to Trigger API Calls

To avoid quickly burning through my free-tier API call limit, I couldn’t trigger the API calls onChange of the input field, as the real Google Translate app does. As such, I needed a manual trigger that would translate into all output languages simultaneously.

  • I created a state variable within a React Context provider, and imported it into the input query component and all translation output components created by the user.

  • When that variable changes, it triggers the API call from each output component, which returns a Promise with the translated text.

I chose this approach because it seemed more manageable than having a central function where all API calls are sent from, and which must then parse any responses and deliver them to the correct output component for display.

With my approach, each output component is independent, but driven by the shared trigger via Context.

Lessons Learned

Intro to Backend Basics - Express.js and Environment Variables

My React education prior to this included nothing about handling API credential security on the front end. I realized this would be an issue after building out the API logic, and considering that my credentials were hard coded into my API calling function, which would be exposed to the client.

Looking up how to resolve this security concern led me to learning about .env variables, but also how that was insufficient if running on the front end.

Finally, I found a tutorial on how to resolve this by creating an Express.js server as a simple backend to handle incoming requests, and how to set environment variables with the hosting provider (in my case, Railway) to keep credentials safe from inspection on the client side.

Screenshot of Workout Tracker App

Workout Tracker - Mobile Web App

Tech Stack:

React

TypeScript

Next.js 14

styled-components

Zustand (state management library)

react-hook-form

This app is designed to track workout sessions, saving each session's data in a session history (saved in the browser's localStorage) for referencing in future sessions or viewing entire past sessions.

Project Purpose and Goal

This app idea came from my need to track my workout progress in a structured way, and not finding an app that had the right balance of flexibility and simplicity.

The key features I needed were the ability to log the weight and reps of each exercise in my workout, and automatically reference the values from my previous attempt of that same exercise from my workout history.

Main Challenge: A Highly Modifiable Data Model

I needed to define a data model with the flexibility to delete or edit many its parts, including:

  • an entire session (e.g. delete any saved session in history)
  • the user defined exercises for a given focus area (e.g. changing the list of exercises I can log to my session, mid-session)
  • individual set data, in case of user input errors (e.g. mistyping the count of reps when inputting - something I learned was easy to do when fatigued and distracted).

Solution 1: Zustand for State Management

I had heard great things about Zustand, an alternative state management library to heavier solutions, like Redux, and wanted to give it a try.

Defining the state's shape and named action functions was simple, and these can be accessed throughout the app, without the use of React Context providers.

Solution 2: Local Storage as a Persistent Database

I use the browser’s local storage to store stringified versions of the app's main data objects:

  • the session history after a workout is completed
  • the user-defined exercises of each focus area

This allows the app to "remember" preferred exercises and reference past sessions.

These enable useful features like showing the user's stats of their most recent attempt of the same exercise, which gives them a target to meet or beat.

Solution 3: UI Components for “Edit Mode” and “Delete Confirmation”

After testing earlier versions of the app, I wanted to provide clear guard rails for the user experience of editing data.

I decided to apply a consistent UX upon entering “Edit Mode”, then trigger an alert/dialog pop up with background overlay to obtain the user’s delete confirmation.

I built the dialog with Radix Primitive’s unstyled component library.

Lessons Learned

UI Design is HARD

The difference between looking good and looking “meh” is subtle.

Thankfully, most teams include designers to make these decisions.

On projects like these, I embrace the challenge of building without design files as an opportunity to make UI decisions and sharpen my otherwise dull design skills.

“Pair-programming” with ChatGPT

This was my first time using ChatGPT for programming.

I had played around with DALL-E and ChatGPT for generating other content, but had purposely avoided programming with AI before feeling more confident in my ability to code solo.

The following are sub-themes from my experience of “pair-programming” with ChatGPT:

Learning Through Interaction

In addition to diagnosing errors and suggesting new code, ChatGPT is excellent at explaining how snippets of code work.

  • If ChatGPT made some complicated or questionable suggestions, I asked follow up questions to delve deeper into how the code worked

For example, ChatGPT suggested several complex TypeScript Utility Types that were beyond my experience with TypeScript.

  • One such interaction exposed me to the powerful combo of the “infer” keyword and the x extends y ? true : false   conditional type in TS

Trust, but Verify

I also encountered many occasions where ChatGPT’s suggestions simply didn’t work.

Often these had to do with niche circumstances, such as:

  • Library idiosyncrasies, possibly due to outdated version documentation in its training data
  • Differences in server-side rendering vs client-side, etc.

Landing Page Projects

These pages were built with Figma designs from Frontend Mentor

Marketing Ops Projects

Google Tag Manager Logo - This project was built for Google Tag Manager

Google Tag Manager - Use Parameters to set Tracking Cookies - First Touch, Last Touch, and Concatenated Touch

JavaScript

Google Apps Script Logo - This project was built for Google Apps Script

Google Apps Script - Pull Outreach.io SDR activites into Google Sheet for Custom Reporting

JavaScript