NextCourt: Priority-Based Pickleball Queue Management

About The Project

NextCourt is a Progressive Web App that solves a critical problem at pickleball facilities: players queue remotely but never show up, wasting court time and frustrating those actually present. The solution is a three-tier priority queue system that rewards physical presence.

Priority System:

  • HIGH Priority: All 4 players checked in at facility (within 100m)
  • MEDIUM Priority: 1-3 players checked in at facility
  • LOW Priority: Remote queue with no check-ins

As players arrive and check in via geolocation, their game priority upgrades automatically and moves up the queue in real-time across all connected devices.

Inspiration

The idea emerged from observing recreational facilities struggle with no-shows. Traditional first-come-first-served queues don't account for physical presence—someone can join remotely and hold a spot while people at the facility wait. This creates inefficiency and frustration. The solution: a priority system that automatically rewards showing up.

Technology Stack

Frontend: React 18 with TypeScript, Vite, Tailwind CSS, Zustand state management, React Router v6

Backend: Firebase Firestore (real-time database), Firebase Authentication (email/Google OAuth), Firebase Hosting

Key APIs: Geolocation API for check-in verification, Service Workers for PWA capabilities

Architecture

The app follows a modular architecture with clear separation of concerns: src/ ├── components/ # Reusable UI components ├── screens/ # Full-page views ├── services/ # Business logic layer ├── store/ # Global state management ├── types/ # TypeScript interfaces └── utils/ # Pure utility functions

Key Algorithms

Priority Calculation

function calculatePriority(players: Player[]): Priority {
  const checkedInCount = players.filter(p => p.checkedIn).length;
  if (checkedInCount >= 4) return 'HIGH';
  if (checkedInCount >= 1) return 'MEDIUM';
  return 'LOW';
}

Queue Sorting

Games sort by priority first (HIGH > MEDIUM > LOW), then by timestamp (FIFO within same priority):

function sortGameQueues(queues: GameQueue[]): GameQueue[] {
  return queues
    .filter(game => game.status === 'waiting')
    .sort((a, b) => {
      const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
      if (priorityDiff !== 0) return priorityDiff;
      return a.queuedAt.getTime() - b.queuedAt.getTime();
    });
}

Geolocation Verification

Uses the Haversine formula to calculate great-circle distance between GPS coordinates: \subsection*{Geolocation Verification}

The application uses the Haversine formula to calculate the great-circle distance between two GPS coordinates

Implementation:

function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
  const R = 6371e3;
  const φ1 = lat1 * Math.PI / 180;
  const φ2 = lat2 * Math.PI / 180;
  const Δφ = (lat2 - lat1) * Math.PI / 180;
  const Δλ = (lon2 - lon1) * Math.PI / 180;

  const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
            Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  return R * c;
}

Players must be within 100 meters of the facility to check in successfully.

Wait Time Estimation

[ \text{Wait Time} = \left\lceil \frac{N_{\text{ahead}} \times T_{\text{avg}}}{C_{\text{available}}} \right\rceil ]

Where (N_{\text{ahead}}) is games ahead in queue, (T_{\text{avg}}) is average game duration (15 minutes), and (C_{\text{available}}) is available courts.

Real-Time Synchronization

Firestore's onSnapshot() listeners enable instant updates across all clients. When any user checks in, all connected devices receive the update within milliseconds and automatically re-sort the queue.

onSnapshot(collection(db, 'gameQueues'), (snapshot) => {
  const games = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
  setGames(games);
  updateQueueState(games);
});

What I Learned

Real-Time Systems Complexity: Building real-time queues taught me about eventual consistency and race conditions. I learned to use Firestore transactions for atomic updates and design data structures that minimize write conflicts.

Mobile-First Design: Most users access this on phones at facilities. I learned to design touch-friendly UI (48×48px minimum tap targets), test on actual devices, handle geolocation permissions across platforms, and optimize for slower networks.

TypeScript Benefits: Strict typing caught bugs before runtime. Union types like 'HIGH' | 'MEDIUM' | 'LOW' prevented typos, and interfaces made the codebase self-documenting and guided refactoring.

Security Rules: I learned to write comprehensive Firestore security rules to prevent malicious writes while allowing legitimate access patterns.

User Experience Details: Small touches matter—toast notifications, loading states, clear error messages, visual feedback, and optimistic updates create a polished experience.

Challenges Faced

Challenge 1: Geolocation Reliability

Browser geolocation is unreliable—iOS Safari sometimes takes 30+ seconds or fails entirely, and GPS accuracy varies wildly.

Solution: Increased check-in radius to 100m for GPS drift, added 10-second timeout, provided clear error messages and retry mechanism, and built a demo mode for testing without real location.

Challenge 2: Real-Time Race Conditions

When multiple users check in simultaneously, Firestore writes can conflict, causing lost updates or incorrect priority calculations.

Solution: Used Firestore transactions for atomic updates and designed the data model to minimize concurrent writes to the same document.

Challenge 3: State Management Complexity

Synchronizing Firestore (source of truth), Zustand store (app state), and React component state became complex.

Solution: Established unidirectional data flow (Firestore → Zustand → Components), used Zustand selectors to prevent unnecessary re-renders, and minimized component local state to UI concerns only.

Challenge 4: Testing Geolocation Features

Can't physically be at a facility while developing, and Chrome DevTools location spoofing is buggy.

Solution: Built comprehensive demo mode that simulates facilities, auto-generates games, simulates check-ins every 10 seconds, and allows full feature testing from anywhere.

Challenge 5: Firebase Free Tier Limits

Firestore free tier allows only 50K reads and 20K writes per day. Real-time listeners can consume this quickly.

Solution: Optimized queries with indexes, used query filters to reduce document reads, implemented client-side caching, and designed UI to minimize refetches. Result: supports 100-500 daily active users on free tier.

Challenge 6: Cross-Platform PWA Quirks

PWA installation and behavior differs drastically between iOS Safari, Android Chrome, and desktop browsers.

Solution: Tested on actual devices, configured manifest for each platform's requirements, added fallback for unsupported platforms, and ensured web version is fully functional without installation.

Core Features Implemented

  • Three-tier priority queue system with automatic recalculation
  • Real-time synchronization across all connected devices
  • Geolocation-based check-in verification (100m radius)
  • Progressive Web App installable on all platforms
  • Email/password and Google OAuth authentication
  • Court status board with occupancy tracking
  • Wait time estimation based on queue position
  • Demo mode for testing and demonstrations
  • Comprehensive Firestore security rules
  • Mobile-first responsive design

Technical Specifications

  • Lines of Code: ~3,500+
  • Components: 9 reusable components
  • Screens: 4 full-page views
  • Services: 4 business logic modules
  • Build Time: <5 seconds with Vite
  • Bundle Size: ~200KB gzipped
  • Supported Platforms: iOS, Android, desktop (any modern browser)

Security Implementation

Firestore security rules ensure proper access control:

match /gameQueues/{gameId} {
  allow read: if isSignedIn();
  allow update: if isSignedIn() && (
    request.auth.uid == resource.data.creatorId ||
    request.auth.uid in resource.data.players[*].userId
  );
}

Users can only update games they created or participate in, while maintaining queue transparency through read access.

Results

NextCourt is a production-ready application that solves a real problem for pickleball communities. The priority queue system creates fairness by rewarding players who show up, reducing no-shows and improving court utilization. It provides instant real-time updates, works on any device as a PWA, and scales to hundreds of users on Firebase free tier.

Conclusion

Building NextCourt demonstrated that elegant solutions often come from simple insights—physical presence should matter in a physical sport's queue system. The real challenge was execution: handling real-time synchronization, geolocation quirks, mobile responsiveness, security, and creating polished user experience.

The result is a practical tool that facilities can deploy today. It showcases how modern web technologies (PWA, real-time databases, geolocation) can solve tangible real-world problems while maintaining performance and user experience across all platforms.

Tech Stack: React, TypeScript, Firebase, Tailwind CSS, Vite
Architecture: Modular component-based with real-time synchronization
Deployment: Firebase Hosting with global CDN

Built With

  • autoprefixer
  • cli
  • css
  • date-fns
  • eslint
  • firebase
  • firebase-authentication
  • firebase-cloud-messaging
  • firebase-hosting
  • firestore
  • geolocation-api
  • git
  • html5
  • javascript
  • lucide-react
  • npm
  • permissions-api
  • postcss
  • react
  • react-dom
  • react-hot-toast
  • react-router
  • service-workers
  • tailwind-css
  • typescript
  • vite
  • vite-pwa-plugin
  • web-app-manifest
  • web-storage-api
  • zustand
Share this project:

Updates