ryanmio / PumpkinPal

An open-source companion app for pumpkin growers
https://pumpkinpal.app/
3 stars 0 forks source link
firebase react tailwindcss

PumpkinPal: An Open-Source Pumpkin OTT Weight Calculator App

Live Web App Demo Account

PumpkinPal Logo

🚧 Project Status: Early Access

PumpkinPal is an open-source app that helps competitive pumpkin growers enter and track their pumpkin-growing journey. This app is in development. Feel free to visit the live app at pumpkinpal.app.

🎯 Project Overview

The goal of this project is to develop a user-friendly, open-source application for competitive pumpkin growers. The app's primary users are hobbyists in the giant pumpkin growing community.

🌱 Our Mission

Our mission is to empower the pumpkin growing community by providing a tool that makes the hobby more accessible and enjoyable. We envision a future where every weigh-off is crowded with more and heavier pumpkins, and we're committed to making that vision a reality through the continuous development and improvement of PumpkinPal.

πŸ’Έ Always Free

PumpkinPal is committed to remaining free for all users. We believe in the power of community and the spirit of sharing knowledge and resources. As such, we pledge to keep PumpkinPal free to use, now and always.

Core Features

Export GIF

Tech Stack

Frontend:

Backend:

Libraries:

Graph GIF

Why React? πŸ”„

I opted to develop this app in React because I believe React is the best framework for coding with AI assistance using tools like ChatGPT or Cursor, which leverages GPT-4 via OpenAI's API. React's popularity, component-based architecture, and existing integrations make it an ideal choice for coding with AI agents.

Why Firebase? πŸ”₯

I considered various options, including setting up a MERN (MongoDB, Express.js, React.js, Node.js) stack. However, I ultimately decided to streamline the process with a Backend as a Service (BaaS) provider. Firebase emerged as the clear choice for its simplicity, real-time capabilities, scalability, ease of integration, and cost-effectiveness, making it a perfect fit for a small-scale application like PumpkinPal.

Why Tailwind CSS? 🎨

Tailwind CSS was chosen as the styling framework for PumpkinPal due to its popularity, inline styling approach, and compatibility with AI-assisted code editors like Cursor.

Front End Errors and Notifications 🚦

The application employs react-hot-toast to manage notifications and provide feedback to the user about the success or failure of various operations. This library provides a simple and intuitive API for creating toast notifications from anywhere within the application.

Toast GIF

Error and User Event Tracking with Google Analytics πŸ“Š

PumpkinPal uses Google Analytics to track errors and user events, providing valuable insights into user behavior and system performance. This tracking is implemented using the react-ga4 library, which provides a simple and efficient way to integrate Google Analytics with a React application.

The tracking logic is contained in the error-analytics.js file in the utilities directory. This file exports two main functions: trackError and trackUserEvent.

The GA_CATEGORIES and GA_ACTIONS objects define the categories and actions used in the tracking events. These include user actions like "Register", "Login", and "Add Pumpkin", as well as system actions like "Error".

By tracking both user events and errors, we can monitor the full user journey, from registration and login to adding and updating pumpkins. This comprehensive tracking allows us to identify which features are most used, how users navigate through the app, and where they may encounter difficulties or errors.

The use of categories and actions in the tracking events provides a structured way to analyze the data. For example, we can easily filter events by category to see all user actions or system errors, or by action to see all instances of a specific event like "Register" or "Add Pumpkin".

Furthermore, the inclusion of a dimension indicating whether the app is running in development or production mode allows us to separate testing and real user data, ensuring the accuracy of our analysis.

The error-analytics.js file in the utilities directory provides a centralized location for all tracking logic, making it easy to maintain and update as needed. The trackError and trackUserEvent functions provide a simple and consistent way to send events to Google Analytics, ensuring that all events are tracked in a consistent and reliable manner.

For more details on how to use Google Analytics with PumpkinPal, refer to the GoogleAnalyticsGuide.md file in the utilities directory.

Calculating OTT

The estimated weight of the pumpkin is calculated based on the Over-the-Top (OTT) method, which involves adding the measurements from end to end, side to side, and around the circumference of the pumpkin.

The calculation is made by first adding the three measurements together to get the OTT. The OTT value is then fed into a two-part formula that estimates the weight of the pumpkin based on various growth factors.

The first part of the formula ((14.2 / (1 + 7.3 Math.pow(2, -(ott) / 96))) 3 + (ott / 51) 2.91) - 8 is an empirically derived relationship between OTT and pumpkin weight. The second part 0.993 applies a slight correction factor to the weight.

If the computed weight turns out to be less than 0 (which can happen if someone measures a small/young pumpkin), we set the weight to 0. The result is then rounded to two decimal places to give a more readable weight estimate.

const calculateEstimatedWeight = (endToEnd, sideToSide, circumference, measurementUnit) => {
    let ott = parseFloat(endToEnd) + parseFloat(sideToSide) + parseFloat(circumference);
    if (measurementUnit === 'cm') {
      ott /= 2.54;  // Convert cm to inches
    }
    let weight = (((14.2 / (1 + 7.3 * Math.pow(2, -(ott) / 96))) ** 3 + (ott / 51) ** 2.91) - 8) * 0.993;

    // If weight is less than 0, set it to 0
    if (weight < 0) {
      weight = 0;
    }

    return weight.toFixed(2);  // round to 2 decimal places
};

This formula is the crux of the PumpkinPal app and what enables pumpkin growers to track and predict their pumpkins' weight throughout the growing season based on their measurements.

Field-Friendly Features

The PumpkinPal app incorporates several design principles to ensure ease of use in a field environment, especially while wearing gloves.

  1. Big inputs and touch targets: Recognizing that our users may be interacting with the app with gloves on, we've designed our input fields and touch targets to be large enough to accommodate these circumstances. This is achieved through custom CSS rules that boost the default size attributes of the HTML input elements.

  2. Smart defaults for data entry: The app intelligently fetches the most recent measurement for each pumpkin from the Firebase backend and prepopulates the fields with these values whenever a new measurement is being entered. This functionality is implemented via a useEffect hook in the AddMeasurement component that triggers whenever the selectedPumpkin state changes, so the user can select any pumpkin from the dropdown to have the defaults update automatically.

  3. Dynamic measurement unit selection: The app pulls the user's preferred measurement unit from Firebase and uses it as the default unit. However, the user also has the option to override this default on-the-fly for individual measurements, providing flexibility when it's needed.

Here's what it looks like in action:

Data Entry GIF

Firestore Data Backups

Data resilience and reliability are non-negotiable. PumpkinPal uses Google's cloud ecosystem to maintain data integrity and ensure high availability through automatic, daily snapshots of our entire Firestore backend. This mitigates potential data loss scenarios and provides swift recovery paths.

Implementation Overview

Our data backups use Google Cloud's serverless technology, enhancing scalability, and minimizing overhead. We've implemented a Cloud Function, scheduledFirestoreExport, that operates on a cron-based schedule. This function orchestrates the export of Firestore data, leveraging Firestore's built-in exportDocuments operation and pushing the data to a secure Google Cloud Storage bucket.

Google Cloud Function

A Google Cloud Function, scheduledFirestoreExport, orchestrates the data export pipeline. It interfaces with Firestore's exportDocuments method, initiating a server-side operation that atomically exports Firestore documents to a designated Google Cloud Storage bucket. Here's the function:

const firestore = require('@google-cloud/firestore');
const client = new firestore.v1.FirestoreAdminClient();
const bucket = 'gs://pumpkinpal_backup'

exports.scheduledFirestoreExport = (event, context) => {
  const projectId = process.env.GCLOUD_PROJECT;
  const databaseName = `projects/${projectId}/databases/(default)`;

  return client
    .exportDocuments({
      name: databaseName,
      outputUriPrefix: bucket,
      collectionIds: [],
    })
    .then(responses => {
      const response = responses[0];
      console.log(`Operation Name: ${response['name']}`);
      return response;
    })
    .catch(err => {
      console.error(err);
    });
};

Google Cloud Storage Bucket

The Firestore backups live in a secure Google Cloud Storage bucket. Leveraging the high durability and regional redundancy offered by Google Cloud, our data is replicated across multiple regions in the United States. This setup fortifies our data integrity, ensuring robust data durability and high availability.

Contextual State Management with React Context API

The PumpkinPal application uses React's Context API for state management, providing a way to share state and functionality across components.

Implementation

React's Context API is used to create global state variables that can be accessed from any component. A UserContext is created to store the current user's data and a loading state. This context is provided at the root level of the application, making it accessible to all child components.

import React, { createContext, useState, useEffect } from 'react';
import { auth } from '../firebase';

export const UserContext = createContext();

export function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(user => {
      setUser(user);
      setLoading(false);
    });

    return unsubscribe;
  }, []);

  return (
    <UserContext.Provider value={{ user, loading }}>
      {children}
    </UserContext.Provider>
  );
}

Contributing

We welcome contributions from the community! If you're interested in contributing, please follow these steps:

  1. Fork the repository on GitHub.
  2. Clone your forked repository to your local machine.
  3. Make your changes and commit them to your forked repository.
  4. Submit a pull request with your changes to the main repository.

Please ensure your code adheres to our coding standards and conventions. All contributions are subject to review and approval by the project maintainers.

License

This project is licensed under the Creative Commons Attribution-NonCommercial (CC BY-NC) License. This means you are free to remix, adapt, and build upon this work, but not for commercial purposes. Please remember to give appropriate credit and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.